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