✨ (transaction) possibility to change location
This commit is contained in:
@@ -5,59 +5,115 @@
|
||||
<script lang="ts">
|
||||
import { Component, Prop, Vue } from 'vue-property-decorator'
|
||||
import mapboxgl from 'mapbox-gl/dist/mapbox-gl'
|
||||
|
||||
interface Location {
|
||||
name?: string
|
||||
lat: number
|
||||
lon: number
|
||||
}
|
||||
import { debounce } from 'lodash-es'
|
||||
import ILocation from '@/models/ILocation'
|
||||
|
||||
@Component
|
||||
export default class EarthMap extends Vue {
|
||||
@Prop({ type: Array, default: () => [] })
|
||||
private locations!: Location[]
|
||||
private locations!: ILocation[]
|
||||
@Prop({ type: Boolean, default: false })
|
||||
private defineLocation!: boolean
|
||||
private earth: any | null = null
|
||||
private markerLocation: any | null = null
|
||||
private emit = debounce((earthMap: EarthMap) => {
|
||||
if (!earthMap.markerLocation) {
|
||||
return
|
||||
}
|
||||
const lnglat = earthMap.markerLocation.getLngLat()
|
||||
earthMap.$emit('located', {
|
||||
lat: lnglat.lat,
|
||||
lon: lnglat.lng
|
||||
})
|
||||
}, 150)
|
||||
|
||||
private mounted() {
|
||||
mapboxgl.accessToken = process.env.VUE_APP_MAPBOX_KEY
|
||||
this.initEarthMap()
|
||||
|
||||
if (!this.earth) {
|
||||
return
|
||||
}
|
||||
|
||||
const markers = this.locations.map((location) => [
|
||||
location.lon,
|
||||
location.lat
|
||||
])
|
||||
const latitudes = this.locations.map((location: Location) => location.lat)
|
||||
const longitudes = this.locations.map((location: Location) => location.lon)
|
||||
const minLat = Math.min(...latitudes)
|
||||
const maxLat = Math.min(...latitudes)
|
||||
const minLon = Math.min(...longitudes)
|
||||
const maxLon = Math.min(...longitudes)
|
||||
|
||||
const earth = new mapboxgl.Map({
|
||||
container: 'earth',
|
||||
style: 'mapbox://styles/mapbox/streets-v11',
|
||||
bounds: [
|
||||
markers.forEach((marker) => {
|
||||
const el = document.createElement('i')
|
||||
el.classList.add('marker', 'gg-pin-alt')
|
||||
|
||||
new mapboxgl.Marker(el).setLngLat(marker).addTo(this.earth)
|
||||
})
|
||||
}
|
||||
|
||||
private initEarthMap() {
|
||||
mapboxgl.accessToken = process.env.VUE_APP_MAPBOX_KEY
|
||||
|
||||
const latitudes = this.locations.map((location: ILocation) => location.lat)
|
||||
const longitudes = this.locations.map((location: ILocation) => location.lon)
|
||||
const minLat = Math.min(...latitudes)
|
||||
const maxLat = Math.max(...latitudes)
|
||||
const minLon = Math.min(...longitudes)
|
||||
const maxLon = Math.max(...longitudes)
|
||||
|
||||
// [sw, ne]
|
||||
const bounds = this.locations.length
|
||||
? [
|
||||
[minLon, minLat],
|
||||
[maxLon, maxLat]
|
||||
],
|
||||
]
|
||||
: undefined
|
||||
|
||||
this.earth = new mapboxgl.Map({
|
||||
container: 'earth',
|
||||
style: 'mapbox://styles/mapbox/streets-v11',
|
||||
bounds,
|
||||
fitBoundsOptions: {
|
||||
padding: 15,
|
||||
padding: 30,
|
||||
maxZoom: 12
|
||||
}
|
||||
})
|
||||
|
||||
earth.addControl(new mapboxgl.NavigationControl())
|
||||
this.earth.addControl(new mapboxgl.NavigationControl())
|
||||
const geolocControl = new mapboxgl.GeolocateControl({
|
||||
positionOptions: {
|
||||
enableHighAccuracy: true
|
||||
},
|
||||
trackUserLocation: true
|
||||
})
|
||||
|
||||
markers.forEach((marker) => {
|
||||
new mapboxgl.Marker().setLngLat(marker).addTo(earth)
|
||||
this.earth.addControl(geolocControl)
|
||||
|
||||
if (!this.defineLocation) {
|
||||
return
|
||||
}
|
||||
geolocControl.trigger()
|
||||
|
||||
const el = document.createElement('i')
|
||||
el.classList.add('gg-pin-alt', 'marker', 'marker-location')
|
||||
|
||||
this.markerLocation = new mapboxgl.Marker(el)
|
||||
.setLngLat(this.earth.getCenter())
|
||||
.addTo(this.earth)
|
||||
|
||||
this.earth.on('move', (evt: any) => {
|
||||
this.markerLocation.setLngLat(this.earth.getCenter())
|
||||
this.emit(this)
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
<style lang="scss">
|
||||
@import '../styles/earth-map.css';
|
||||
|
||||
.earth-container {
|
||||
min-height: 50vh;
|
||||
height: 100%;
|
||||
}
|
||||
.marker-location {
|
||||
color: var(--primary-color);
|
||||
z-index: 1;
|
||||
}
|
||||
</style>
|
||||
@@ -94,15 +94,27 @@
|
||||
<label class="label">Localisation</label>
|
||||
</div>
|
||||
<div class="field-body">
|
||||
<div class="field">
|
||||
<div class="control" v-if="transaction.location">
|
||||
<div
|
||||
class="field"
|
||||
:class="{ 'has-addons': !!transaction.location }"
|
||||
>
|
||||
<div class="control">
|
||||
<input
|
||||
type="text"
|
||||
readonly
|
||||
class="input"
|
||||
v-if="transaction.location"
|
||||
v-model="transaction.location.place"
|
||||
/>
|
||||
</div>
|
||||
<div class="control set-location">
|
||||
<button
|
||||
class="button is-primary"
|
||||
@click="displayLocationModal = true"
|
||||
>
|
||||
définir
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -176,6 +188,33 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal" :class="{ 'is-active': displayLocationModal }">
|
||||
<div class="modal-background"></div>
|
||||
<div class="modal-card">
|
||||
<section>
|
||||
<earth-map
|
||||
v-if="displayLocationModal"
|
||||
:locations="
|
||||
transaction.location ? [transaction.location] : undefined
|
||||
"
|
||||
:define-location="true"
|
||||
@located="located"
|
||||
/>
|
||||
</section>
|
||||
<footer class="modal-card-foot">
|
||||
<button
|
||||
class="button is-primary"
|
||||
:class="{ 'is-loading': isSetLocation }"
|
||||
@click="validLocation"
|
||||
>
|
||||
valider
|
||||
</button>
|
||||
<button class="button" @click="displayLocationModal = false">
|
||||
annuler
|
||||
</button>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
<fab-button
|
||||
@valid="submitTransaction"
|
||||
:margin="true"
|
||||
@@ -203,11 +242,14 @@ import formatDate from '@/utils/format-date'
|
||||
import notif from '@/utils/notif'
|
||||
import { money } from '@/utils/filters'
|
||||
import { confirmation, alertMessage, findContrastColor } from '@/utils'
|
||||
import ILocation from '@/models/ILocation'
|
||||
import MapService from '../services/MapService'
|
||||
|
||||
const today: Date = new Date()
|
||||
|
||||
@Component({
|
||||
components: {
|
||||
'earth-map': () => import('@/components/EarthMap.vue'),
|
||||
'fab-button': () => import('@/components/FabButton.vue'),
|
||||
'transaction-split': () => import('@/components/TransactionSplit.vue'),
|
||||
'transaction-tag-update': () =>
|
||||
@@ -226,13 +268,15 @@ export default class TransactionCreate extends Vue {
|
||||
public tag: TransactionTag = TransactionTag.None
|
||||
public today: string = formatDate(today)
|
||||
public date: string = formatDate(today)
|
||||
public location: string = ''
|
||||
public currency: ICurrency | null = null
|
||||
public payBy: string = ''
|
||||
public payFor: ISplit[] = []
|
||||
public maxAmount: number = 1000000
|
||||
public noTag: string = TransactionTag.None
|
||||
public transactionTagLabel: typeof TransactionTagLabel = TransactionTagLabel
|
||||
public displayLocationModal = false
|
||||
public isSetLocation = false
|
||||
public location: ILocation | null = null
|
||||
|
||||
public async created(): Promise<void> {
|
||||
this.setData(this.transaction)
|
||||
@@ -325,6 +369,29 @@ export default class TransactionCreate extends Vue {
|
||||
this.payFor.forEach((p: ISplit) => (p.weight = p.weight || 1))
|
||||
}
|
||||
|
||||
public located(location: ILocation) {
|
||||
this.location = location
|
||||
}
|
||||
|
||||
public async validLocation() {
|
||||
try {
|
||||
this.isSetLocation = true
|
||||
if (this.location) {
|
||||
const place = await MapService.getPlace(this.location)
|
||||
const location = {
|
||||
...this.location,
|
||||
place
|
||||
}
|
||||
this.$set<ILocation>(this.transaction, 'location', location)
|
||||
}
|
||||
} catch (error) {
|
||||
this.$set(this.transaction, 'location', this.transaction.location)
|
||||
} finally {
|
||||
this.displayLocationModal = false
|
||||
this.isSetLocation = false
|
||||
}
|
||||
}
|
||||
|
||||
public get payForUsers(): ISplit[] {
|
||||
return this.payFor.filter((p, index) => p.weight > 0)
|
||||
}
|
||||
@@ -394,4 +461,7 @@ export default class TransactionCreate extends Vue {
|
||||
.help {
|
||||
text-align: left;
|
||||
}
|
||||
.set-location {
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -4,7 +4,25 @@ import ILocationQuery from '@/models/ILocationQuery'
|
||||
class MapService {
|
||||
private key: string = process.env.VUE_APP_MAP_KEY || ''
|
||||
|
||||
public async getLocation(location: ILocation): Promise<string> {
|
||||
public async getPosition(): Promise<ILocation | null> {
|
||||
const position = await this.getCurrentPosition()
|
||||
if (!position) {
|
||||
return null
|
||||
}
|
||||
const { coords } = position
|
||||
const location = {
|
||||
lat: coords.latitude,
|
||||
lon: coords.longitude
|
||||
}
|
||||
const place = await this.getPlace(location)
|
||||
|
||||
return {
|
||||
...location,
|
||||
place
|
||||
}
|
||||
}
|
||||
|
||||
public async getPlace(location: ILocation): Promise<string> {
|
||||
const result = await fetch(this.url(location.lat, location.lon))
|
||||
if (!result) {
|
||||
return ''
|
||||
@@ -25,6 +43,15 @@ class MapService {
|
||||
private url(latitude: number, longitude: number) {
|
||||
return `https://dev.virtualearth.net/REST/v1/Locations/${latitude},${longitude}?key=${this.key}`
|
||||
}
|
||||
|
||||
private getCurrentPosition(): Promise<Position | null> {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!('geolocation' in navigator)) {
|
||||
resolve(null)
|
||||
}
|
||||
return navigator.geolocation.getCurrentPosition(resolve, reject)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default new MapService()
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// sass-lint:disable final-newline empty-line-between-blocks class-name-format no-url-protocols no-url-domains nesting-depth
|
||||
@charset 'utf-8';
|
||||
@import url('https://fonts.googleapis.com/css?family=Nunito|Raleway|Open+Sans&display=swap');
|
||||
@import url('https://css.gg/c?=|pin-alt');
|
||||
@import './framework';
|
||||
@import './transitions';
|
||||
|
||||
:root {
|
||||
--primary-color: #{$primary};
|
||||
--primary-font-color: #{$main};
|
||||
|
||||
@@ -78,9 +78,3 @@ export const toHex = (text: string): string => {
|
||||
.join('')
|
||||
)
|
||||
}
|
||||
|
||||
export const getCurrentPosition = (options = {}): Promise<Position> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
navigator.geolocation.getCurrentPosition(resolve, reject, options)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ import ITransaction from '@/models/ITransaction'
|
||||
import IUser from '@/models/IUser'
|
||||
import accountService from '@/services/AccountService'
|
||||
import transactionService from '@/services/TransactionService'
|
||||
import { getCurrentPosition } from '@/utils'
|
||||
import mapService from '@/services/MapService'
|
||||
|
||||
const today: Date = new Date()
|
||||
@@ -48,25 +47,15 @@ export default class TransactionNew extends Vue {
|
||||
}
|
||||
|
||||
try {
|
||||
if ('geolocation' in navigator) {
|
||||
getCurrentPosition().then(({ coords }) => {
|
||||
mapService
|
||||
.getLocation({
|
||||
lat: coords.latitude,
|
||||
lon: coords.longitude
|
||||
})
|
||||
.then((place) => {
|
||||
if (!this.transaction) {
|
||||
return
|
||||
}
|
||||
this.$set(this.transaction, 'location', {
|
||||
lat: coords.latitude,
|
||||
lon: coords.longitude,
|
||||
place
|
||||
})
|
||||
})
|
||||
})
|
||||
const location = await mapService.getPosition()
|
||||
|
||||
if (!location) {
|
||||
return
|
||||
}
|
||||
this.$set(this.transaction, 'location', { ...location })
|
||||
} catch (error) {
|
||||
// tslint:disable-next-line: no-console
|
||||
console.error(error)
|
||||
|
||||
Reference in New Issue
Block a user