diff --git a/src/components/EventForm.vue b/src/components/EventForm.vue index aa8be1507ba48f67a0a5a007c6656874aeb673cb..c6e10a0224b7c0432f4f959c9d69b897a0a9037f 100644 --- a/src/components/EventForm.vue +++ b/src/components/EventForm.vue @@ -1,6 +1,6 @@ <template lang="pug"> q-card.full-width - //- Create Event Header + //- 'Create Event' Header q-card-section.bg-blue-grey-1(style='padding: 8px') span.text-subtitle1.text-weight-light.text-blue-grey-10.q-ml-sm Create your event q-chip.float-right( @@ -11,12 +11,12 @@ q-card.full-width icon='done', style='margin-top: -1px' ) Submit Event - //- Create Event Form + //- 'Create Event' Form q-card-section //- Event name field .row.q-pb-md q-input.full-width( - v-model='name', + v-model='eventData.name', rounded, outlined, :dense='true', @@ -28,9 +28,17 @@ q-card.full-width //- Date and Time field span.text-subtitle1.text-blue-grey-10.q-pt-sm Date and Time: .row.q-pb-md.no-wrap - q-input(v-model='date', rounded, outlined, type='date', :dense='true', hint='Date of Event', style='width: 60%') + q-input( + v-model='eventData.date', + rounded, + outlined, + type='date', + :dense='true', + hint='Date of Event', + style='width: 60%' + ) q-input.q-ml-sm( - v-model='time', + v-model='eventData.time', rounded, outlined, type='time', @@ -41,63 +49,207 @@ q-card.full-width //- Location of Event field .row.q-pb-md span.text-subtitle1.text-blue-grey-10 Where?: - q-input.full-width(rounded, outlined, v-model='places', label='Search places', :dense='true') + q-select.full-width( + v-model='location', + @input-value='setUserInput', + :options='placesOptions', + :dense='true', + label='Search places', + rounded, + outlined, + use-input, + input-debounce='0', + emit-value, + map-options + ) template(v-slot:prepend) q-icon(name='place') - template(v-slot:append) - q-icon.cursor-pointer(name='search', @click='searchPlaces()') + template(v-slot:no-option) + q-item + q-item-section.text-grey No results //- Invite friends field .row.q-pb-md span.text-subtitle1.text-blue-grey-10 Invite your friends: - q-input.full-width(rounded, outlined, v-model='friends', label='Search friends', :dense='true') + q-select.full-width( + v-model='eventData.friends', + :dense='true', + :options='friendsOptions', + label='Select friends', + rounded, + outlined, + multiple, + use-chips, + emit-value, + map-options + ) template(v-slot:prepend) q-icon(name='person_add') - template(v-slot:append) - q-icon.cursor-pointer(name='search', @click='searchFriends()') - //- Event icon field + //- Event choose icon field .text-subtitle1.text-blue-grey-10 Choose an icon for your Event: .row.q-pb-md.justify-around - img(src='https://img.icons8.com/dusk/50/000000/home.png') - img(src='https://img.icons8.com/dusk/50/000000/party-baloons.png') - img(src='https://img.icons8.com/dusk/50/000000/dancing-party.png') - img(src='https://img.icons8.com/dusk/50/000000/champagne.png') - img(src='https://img.icons8.com/dusk/50/000000/dj.png') - img(src='https://img.icons8.com/dusk/50/000000/gift.png') - //- Event details field + img.cursor-pointer.q-pa-xs( + v-for='eventType in ["Home", "Baloons", "Dancing", "Champagne", "Dj", "Gift"]', + @click='eventData.type = eventType', + :src='getEventIcon(eventType)', + :style='eventData.type == eventType ? "box-shadow: 0 0 1pt 2pt #03c6fc; border-radius: 30%" : ""' + ) + //- Event insert details field .row span.text-subtitle1.text-blue-grey-10 Details: q-input.full-width( rounded, outlined, autogrow, - v-model='details', + v-model='eventData.details', placeholder='Dress code, specifications, directions etc.', :dense='true' ) </template> <script> +import { mapState, mapActions } from 'vuex' +import axios from 'axios' + export default { data() { return { - name: null, - date: null, - time: null, - places: null, - friends: null, - details: null + eventData: { + name: '', + date: null, + time: null, + friends: [], + type: '', + details: '' + }, + location: '', + userInput: 'a', + service: null, + searchResults: [] } }, + methods: { - searchPlaces() { - console.log('searching places') + async submitEvent() { + // Turn place name into coordinates to save in db + await this.geocodeLocation() + // Need to turn array into object before saving to db + this.eventData['friends'] = this.friendsObject + // Save eventData object under events node in db + this.firebaseSubmitEvent(this.eventData) + }, + + placesGetPredictions() { + this.service.getPlacePredictions( + { + input: this.userInput, + location: new google.maps.LatLng(this.center), + radius: 5000 + }, + this.savePredictions + ) }, - searchFriends() { - console.log('searching friends') + + savePredictions(predictions, status) { + if (status !== window.google.maps.places.PlacesServiceStatus.OK) { + this.searchResults = [] + return + } + this.searchResults = predictions + }, + + async geocodeLocation() { + const URL = `https://secret-ocean-49799.herokuapp.com/https://maps.googleapis.com/maps/api/place/details/json?place_id=${this.location}&fields=geometry&key=AIzaSyBPUdoB3wV6A9L-H1-J5POiQRmgqqcL9Bk` + + await axios + .get(URL) + .then(response => { + let placeLocation = response.data.result.geometry.location + + this.eventData['lat'] = placeLocation.lat + this.eventData['lng'] = placeLocation.lng + }) + .catch(error => { + console.log(error) + }) }, - submitEvent() { - console.log('submit event') + + getEventIcon(eventType) { + switch (eventType) { + case 'Home': + return 'https://img.icons8.com/dusk/50/000000/home.png' + case 'Baloons': + return 'https://img.icons8.com/dusk/50/000000/party-baloons.png' + case 'Dancing': + return 'https://img.icons8.com/dusk/50/000000/dancing-party.png' + case 'Champagne': + return 'https://img.icons8.com/dusk/50/000000/champagne.png' + case 'Dj': + return 'https://img.icons8.com/dusk/50/000000/dj.png' + case 'Gift': + return 'https://img.icons8.com/dusk/50/000000/gift.png' + } + }, + + setUserInput(val) { + this.userInput = val + }, + + ...mapActions('firebase', ['firebaseSubmitEvent']) + }, + + computed: { + friendsOptions() { + var optionsArray = [] + + for (const key in this.friends) { + let optionObject = { + label: this.friends[key].name, + value: key + } + optionsArray.push(optionObject) + } + return optionsArray + }, + + placesOptions() { + var optionsArray = [] + + for (const key in this.searchResults) { + let result = this.searchResults[key] + + let optionObject = { + label: result.description, + value: result.place_id + } + optionsArray.push(optionObject) + } + return optionsArray + }, + + friendsObject() { + let friendsObj = {} + + this.eventData.friends.forEach(friendKey => { + friendsObj[friendKey] = true + }) + + return friendsObj + }, + + ...mapState('firebase', ['friends', 'center']) + }, + + watch: { + userInput(newValue) { + if (newValue) { + this.placesGetPredictions() + } } + }, + + mounted() { + this.service = new window.google.maps.places.AutocompleteService() + this.placesGetPredictions() // Default predict with letter 'a' as input } } </script> \ No newline at end of file diff --git a/src/components/GoogleMap.vue b/src/components/GoogleMap.vue index ea39366fd8bad77c449e413020957b99cd36b899..723e7659b67f5684d22fc53d3759a1709ab5dbd6 100644 --- a/src/components/GoogleMap.vue +++ b/src/components/GoogleMap.vue @@ -31,7 +31,8 @@ export default { map: null, heatmap: null, places: [], - crimes: [] + crimes: [], + dataFetched: false } }, @@ -200,8 +201,7 @@ export default { map: map }) - // Save heatmap and map instance for use in toggleHeatmap() - this.heatmap = heatmap + // Save map instance for global access in other methods this.map = map // Wait for google Places API to be finished fetching relevant data @@ -209,12 +209,6 @@ export default { // Mark all relevant places on the map this.mapsInitPlaces() - // Add all signals saved in the store on the map - for (const i in this.signals) { - let signal = this.signals[i] - this.mapsAddSignal(signal) - } - // Wait for police.uk API to return crime data await this.getCrimeData() // Create a heatmap based on crimes location @@ -230,6 +224,23 @@ export default { }) heatmap.set('radius', heatmap.get('radius') ? null : 80) heatmap.set('opacity', heatmap.get('opacity') ? null : 0.3) + + // Save heatmap reference for user in toggleHeatmap() + this.heatmap = heatmap + + // Add all signals saved in the store on the map + for (const i in this.signals) { + let signal = this.signals[i] + this.mapsAddSignal(signal) + } + + // Add all event saved in the store on the map + for (const i in this.events) { + let event = this.events[i] + this.mapsAddEvent(event) + } + + this.dataFetched = true }, /* Creates markers and infoWindows for every relevant nightlife establishment in the area */ @@ -291,12 +302,50 @@ export default { return 'https://img.icons8.com/color/50/000000/car-rental.png' } }, + + /* Code snippet to add a marker and info window on the map for an event */ + mapsAddEvent(event) { + var infoWindow = new google.maps.InfoWindow() + + let marker = new google.maps.Marker({ + position: new google.maps.LatLng(event.lat, event.lng), + map: this.map, + icon: this.getEventIcon(event.type) + }) + + google.maps.event.addListener(marker, 'click', () => { + infoWindow.setContent( + `<p class="text-subtitle1" style="margin-bottom:8px;">${event.name} (${event.date} ${event.time}) </p> + <p class="text-weight-light">${event.details}</p>` + ) + infoWindow.open(this.map, marker) + }) + }, + + /* Returns the image source for every type of event */ + getEventIcon(eventType) { + switch (eventType) { + case 'Home': + return 'https://img.icons8.com/dusk/50/000000/home.png' + case 'Baloons': + return 'https://img.icons8.com/dusk/50/000000/party-baloons.png' + case 'Dancing': + return 'https://img.icons8.com/dusk/50/000000/dancing-party.png' + case 'Champagne': + return 'https://img.icons8.com/dusk/50/000000/champagne.png' + case 'Dj': + return 'https://img.icons8.com/dusk/50/000000/dj.png' + case 'Gift': + return 'https://img.icons8.com/dusk/50/000000/gift.png' + } + }, + ...mapActions('firebase', ['firebaseSavePosition']) }, computed: { ...mapState(['mapStyles']), - ...mapState('firebase', ['center', 'signals', 'latestSignalKey']) + ...mapState('firebase', ['center', 'signals', 'latestSignalKey', 'events', 'latestEventKey']) }, watch: { @@ -305,8 +354,22 @@ export default { deep: true, // Whenever a new signal gets added, add it to the map handler() { - let signal = this.signals[this.latestSignalKey] - this.mapsAddSignal(signal) + // If db signals have been fetched start tracking new events + if (this.dataFetched === true) { + let signal = this.signals[this.latestSignalKey] + this.mapsAddSignal(signal) + } + } + }, + events: { + deep: true, + // Whenever a new event gets added, add it to the map + handler() { + // If db events have been fetched start tracking new events + if (this.dataFetched === true) { + let event = this.events[this.latestEventKey] + this.mapsAddEvent(event) + } } } }, diff --git a/src/store/firebase.js b/src/store/firebase.js index d8d82adbdfd6c70982df919c29ca051334f0b9cc..4b4ad20ff418b403edada6c7bb556477d58068b0 100644 --- a/src/store/firebase.js +++ b/src/store/firebase.js @@ -9,7 +9,9 @@ const state = { friends: {}, pending: {}, signals: {}, - latestSignalKey: null + latestSignalKey: null, + events: {}, + latestEventKey: null } const mutations = { @@ -59,6 +61,13 @@ const mutations = { let signalId = Object.keys(payload)[0] state.signals[signalId] = payload[signalId] state.latestSignalKey = signalId + }, + + // Adds an event in 'events' object + addEvent(state, payload) { + let eventId = Object.keys(payload)[0] + state.events[eventId] = payload[eventId] + state.latestEventKey = eventId } } @@ -124,6 +133,8 @@ const actions = { dispatch('firebaseGetFriends', userId) // Get all signals in db and save them in the store dispatch('firebaseGetSignals') + // Get all signals in db and save them in the store + dispatch('firebaseGetEvents') // Handle online status firebaseDb.ref('.info/connected').on('value', (snap) => { @@ -241,7 +252,7 @@ const actions = { }) }, - /* Add signal under signals node in firebase's sb */ + /* Add signal under signals node in firebase's db */ firebaseSendSignal({ state, commit }, payload) { let signal = payload signal['lat'] = state.center.lat @@ -256,6 +267,30 @@ const actions = { commit('addSignal', newSignal) }, + /*********************** + EVENTS + ************************/ + /* Fetch from db all events and save them to the store while adding + a listener to automatically add new recorded events */ + firebaseGetEvents({ commit }) { + firebaseDb.ref('events/').on('child_added', snapshot => { + let event = {} + event[snapshot.key] = snapshot.val() + commit('addEvent', event) + }) + }, + + /* Add an event under events node in firebase's db */ + firebaseSubmitEvent({ commit }, payload) { + let event = payload + // Add event to db + let eventRef = firebaseDb.ref('events/').push(event) + + let newEvent = {} + newEvent[eventRef.key] = event // {eventKey: eventContent} + // Add event to the store's 'event' object + commit('addEvent', newEvent) + }, /*********************** POSITION ************************/