Skip to content

Commit

Permalink
street view panorama component
Browse files Browse the repository at this point in the history
  • Loading branch information
xkjyeah committed Jan 2, 2017
1 parent 73b135e commit c1bdfc7
Show file tree
Hide file tree
Showing 10 changed files with 283 additions and 32 deletions.
1 change: 1 addition & 0 deletions build-copy.js
Expand Up @@ -5,3 +5,4 @@ cp('src/components/infoWindow.vue', 'dist/components/')
cp('src/components/map.vue', 'dist/components/')
cp('src/components/placeInput.vue', 'dist/components/')
cp('src/components/autocomplete.vue', 'dist/components/')
cp('src/components/streetViewPanorama.vue', 'dist/components/')
3 changes: 3 additions & 0 deletions examples/index.html
Expand Up @@ -39,6 +39,9 @@ <h1>Vue2 Google Maps - Examples</h1>
<li>
<a href="basic-map-functions.html">Map Functions</a>.
</li>
<li>
<a href="stree-view-pano.html">Street View Panorama</a> (Experimental).
</li>
</ol>
</body>
</html>
64 changes: 64 additions & 0 deletions examples/street-view-pano.html
@@ -0,0 +1,64 @@
<head>
<style>
.pano {
width: 500px;
height: 300px;
}
</style>
</head>
<body>
<div id="root">
<h1>Panorama at Pembroke College, Cambridge, facing North, looking slightly upwards (10 degrees)</h1>
Point-of-view updates when you pan around

<gmap-street-view-panorama
class="pano"
:position="{lat: 52.201272, lng: 0.118720}"
:pov="{heading: 0, pitch: 10}"
:zoom="1"
@pano_changed="updatePano"
@pov_changed="updatePov">
</gmap-street-view-panorama>
<ul>
<li>Heading: {{pov && pov.heading}}</li>
<li>Pitch: {{pov && pov.pitch}}</li>
<li>Pano ID: {{pano}}</li>
</ul>
</div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.6/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.16.4/lodash.js"></script>
<script src="vue-google-maps.js"></script>

<script>

Vue.use(VueGoogleMap, {
load: {
key: 'AIzaSyBzlLYISGjL_ovJwAehh6ydhB56fCCpPQw',
libraries: 'places'
},
// Demonstrating how we can customize the name of the components
installComponents: true,
});

document.addEventListener('DOMContentLoaded', function() {
new Vue({
el: '#root',
data: {
pov: null,
pano: null,
},
methods: {
updatePov(pov) {
this.pov = pov;
},
updatePano(pano) {
this.pano = pano;
}
}
});
});

</script>

</body>
6 changes: 6 additions & 0 deletions src/components/mapElementMixin.js
Expand Up @@ -16,6 +16,12 @@ import Map from './map.vue'
export default {

mixins: [DeferredReadyMixin],

data() {
return {
_changeIndicators: {} // For propsBinder trackProperties
}
},

created() {
/* Search for the Map component in the parent */
Expand Down
23 changes: 4 additions & 19 deletions src/components/mapImpl.js
Expand Up @@ -6,6 +6,7 @@ import eventsBinder from '../utils/eventsBinder.js';
import propsBinder from '../utils/propsBinder.js';
import {DeferredReady} from '../utils/deferredReady.js'
import getPropsMixin from '../utils/getPropsValuesMixin.js'
import latlngChangedHandler from '../utils/latlngChangedHandler.js';

const props = {
center: {
Expand Down Expand Up @@ -108,27 +109,11 @@ export default {
watch: {
center: {
deep: true,
handler(val, oldVal) {
// Observed bug with Vue 2.1.6 under certain circumstances:
// If you pass an object constant into :center, the deep watch
// is still triggered
function isChanged(prop) {
var oldProp = (typeof oldVal[prop] === 'number') ? oldVal[prop] :
(typeof oldVal[prop] === 'function') ? oldVal[prop].apply(oldVal) :
oldVal[prop]; // don't know how to handle
var newProp = (typeof val[prop] === 'number') ? val[prop] :
(typeof val[prop] === 'function') ? val[prop].apply(val) :
val[prop]; // don't know how to handle
return oldProp !== newProp;
}

handler: latlngChangedHandler((val, oldVal) => {
if (this.$mapObject) {
// Check if the value has really changed
if (isChanged('lat') || isChanged('lng')) {
this.$mapObject.setCenter(val);
}
this.$mapObject.setCenter(val);
}
}
}),
},
zoom(zoom) {
if (this.$mapObject) {
Expand Down
20 changes: 20 additions & 0 deletions src/components/streetViewPanorama.vue
@@ -0,0 +1,20 @@
<template>
<div class="vue-street-view-pano-container">
<div ref="vue-street-view-pano" class="vue-street-view-pano"></div>
<slot></slot>
</div>
</template>

<script src="./streetViewPanoramaImpl.js">
</script>

<style lang="css">
.vue-street-view-pano-container {
position: relative;
}
.vue-street-view-pano-container .vue-street-view-pano {
left: 0; right: 0; top: 0; bottom: 0;
position: absolute;
}
</style>
125 changes: 125 additions & 0 deletions src/components/streetViewPanoramaImpl.js
@@ -0,0 +1,125 @@
import _ from 'lodash';

import {loaded} from '../manager.js';
import {DeferredReadyMixin} from '../utils/deferredReady.js';
import eventsBinder from '../utils/eventsBinder.js';
import propsBinder from '../utils/propsBinder.js';
import {DeferredReady} from '../utils/deferredReady.js'
import getPropsMixin from '../utils/getPropsValuesMixin.js'
import latlngChangedHandler from '../utils/latlngChangedHandler.js';

const props = {
zoom: {
twoWay: true,
type: Number
},
pov: {
twoWay: true,
type: Object,
trackProperties: ['pitch', 'heading']
},
position: {
twoWay: true,
type: Object,
},
pano: {
twoWay: true,
type: String
},
motionTracking: {
twoWay: false,
type: Boolean
},
visible: {
twoWay: false,
type: Boolean,
default: true,
},
options: {
twoWay: false,
type: Object,
default () {return {}}
}
};

const events = [
'closeclick',
'status_changed',
];

// Other convenience methods exposed by Vue Google Maps
const customMethods = {
resize() {
if (this.$panoObject) {
google.maps.event.trigger(this.$panoObject, 'resize');
}
},
};

// Methods is a combination of customMethods and linkedMethods
const methods = _.assign({}, customMethods);

export default {
mixins: [getPropsMixin, DeferredReadyMixin],
props: props,
replace: false, // necessary for css styles
methods,

created() {
this.$panoCreated = new Promise((resolve, reject) => {
this.$panoCreatedDeferred = {resolve, reject}
});
},

data() {
return {
_changeIndicators: {} // For propsBinder trackProperties
}
},

watch: {
position: {
deep: true,
handler: latlngChangedHandler((val, oldVal) => {
if (this.$panoObject) {
this.$panoObject.setCenter(val);
}
}),
},
zoom(zoom) {
if (this.$panoObject) {
this.$panoObject.setZoom(zoom);
}
}
},

deferredReady() {
return loaded.then(() => {
// getting the DOM element where to create the map
const element = this.$refs['vue-street-view-pano'];

// creating the map
const options = _.defaults({},
_.omit(this.getPropsValues(), ['options']),
this.options
);
console.log(options);

this.$panoObject = new google.maps.StreetViewPanorama(element, options);

// binding properties (two and one way)
propsBinder(this, this.$panoObject,
_.omit(props, ['position', 'zoom']));

//binding events
eventsBinder(this, this.$panoObject, events);

this.$panoCreatedDeferred.resolve(this.$panoObject);

return this.$panoCreated;
})
.catch((error) => {
throw error;
});
},
}
2 changes: 2 additions & 0 deletions src/main.js
Expand Up @@ -10,6 +10,7 @@ import _ from 'lodash'
// Vue component imports
import InfoWindow from './components/infoWindow.vue'
import Map from './components/map.vue';
import StreetViewPanorama from './components/streetViewPanorama.vue';
import PlaceInput from './components/placeInput.vue'
import Autocomplete from './components/autocomplete.vue'

Expand Down Expand Up @@ -42,5 +43,6 @@ export function install(Vue, options) {
Vue.component('GmapRectangle', Rectangle);
Vue.component('GmapAutocomplete', Autocomplete);
Vue.component('GmapPlaceInput', PlaceInput);
Vue.component('GmapStreetViewPanorama', StreetViewPanorama);
}
}
21 changes: 21 additions & 0 deletions src/utils/latlngChangedHandler.js
@@ -0,0 +1,21 @@
// Observed bug with Vue 2.1.6 under certain circumstances:
// If you pass an object constant into :center, the deep watch
// is still triggered
function isChanged(prop, val, oldVal) {
var oldProp = (typeof oldVal[prop] === 'number') ? oldVal[prop] :
(typeof oldVal[prop] === 'function') ? oldVal[prop].apply(oldVal) :
oldVal[prop]; // don't know how to handle
var newProp = (typeof val[prop] === 'number') ? val[prop] :
(typeof val[prop] === 'function') ? val[prop].apply(val) :
val[prop]; // don't know how to handle
return oldProp !== newProp;
}

export default function handler(action) {
return (val, oldVal) => {
// Check if the value has really changed
if (isChanged('lat', val, oldVal) || isChanged('lng', val, oldVal)) {
action(val, oldVal);
}
}
}
50 changes: 37 additions & 13 deletions src/utils/propsBinder.js
Expand Up @@ -8,8 +8,8 @@ function capitalizeFirstLetter(string) {

export default (vueElement, googleMapsElement, props, options) => {
options = options || {};
var {afterModelChanged : afterModelChanged} = options;
_.forEach(props, ({twoWay: twoWay, type:type}, attribute) => {
var {afterModelChanged} = options;
_.forEach(props, ({twoWay, type, trackProperties}, attribute) => {
const setMethodName = 'set' + capitalizeFirstLetter(attribute);
const getMethodName = 'get' + capitalizeFirstLetter(attribute);
const eventName = attribute.toLowerCase() + '_changed';
Expand All @@ -19,17 +19,41 @@ export default (vueElement, googleMapsElement, props, options) => {
// although this may really be the user's responsibility
var timesSet = 0;

vueElement.$watch(attribute, () => {
const attributeValue = vueElement[attribute];

timesSet++;
googleMapsElement[setMethodName](attributeValue);
if (afterModelChanged) {
afterModelChanged(attribute, attributeValue);
}
}, {
deep: type === Object
});
if (type !== Object || !trackProperties) {
// Track the object deeply
vueElement.$watch(attribute, () => {
const attributeValue = vueElement[attribute];

timesSet++;
googleMapsElement[setMethodName](attributeValue);
if (afterModelChanged) {
afterModelChanged(attribute, attributeValue);
}
}, {
deep: type === Object
});
} else if (type === Object && trackProperties) {
// The indicator variable that is updated whenever any of the properties have changed
// This ensures that the event handler will only be fired once
let attributeTrackerName = `_${attribute}_changeTracker`;
let attributeTrackerRoot = '$data._changeIndicators'

vueElement.$set(vueElement.$data._changeIndicators, attributeTrackerName, 0);

vueElement.$watch(attributeTrackerRoot + '.' + attributeTrackerName, () => {
googleMapsElement[setMethodName](vueElement[attribute]);
if (afterModelChanged) {
afterModelChanged(attribute, attributeValue);
}
})

trackProperties.forEach(propName => {
vueElement.$watch(`${attribute}.${propName}`, () => {
vueElement.$set(attributeTrackerRoot, attributeTrackerName,
vueElement.$get(attributeTrackerRoot, attributeTrackerName) + 1);
})
})
}

if (twoWay) {
googleMapsElement.addListener(eventName, (ev) => {
Expand Down

0 comments on commit c1bdfc7

Please sign in to comment.