├── ui
├── src
│ ├── mixins
│ │ ├── .gitkeep
│ │ ├── map-element.js
│ │ └── mountable.js
│ ├── components
│ │ ├── Component.sass
│ │ ├── Component.js
│ │ ├── place-input.vue
│ │ ├── QGoogleMap.vue
│ │ ├── info-window.vue
│ │ ├── autocomplete.vue
│ │ ├── street-view-panorama.vue
│ │ ├── kml-layer.js
│ │ ├── map.vue
│ │ ├── rectangle.js
│ │ ├── circle.js
│ │ ├── polyline.js
│ │ ├── marker.js
│ │ ├── cluster.js
│ │ └── polygon.js
│ ├── index.sass
│ ├── utils
│ │ ├── bind-events.js
│ │ ├── lazy-value.js
│ │ ├── mapped-props-to-vue-props.js
│ │ ├── watch-primitive-properties.js
│ │ ├── simulate-arrow-down.js
│ │ ├── two-way-binding-wrapper.js
│ │ └── bind-props.js
│ ├── factories
│ │ ├── promise-lazy.js
│ │ └── map-element.js
│ ├── components-implementation
│ │ ├── info-window.js
│ │ ├── place-input.js
│ │ ├── autocomplete.js
│ │ ├── street-view-panorama.js
│ │ └── map.js
│ ├── init
│ │ └── initializer.js
│ └── index.js
├── build
│ ├── entry
│ │ ├── index.common.js
│ │ ├── index.esm.js
│ │ └── index.umd.js
│ ├── script.open-umd.js
│ ├── script.clean.js
│ ├── config.js
│ ├── index.js
│ ├── utils.js
│ ├── script.app-ext.js
│ ├── script.css.js
│ └── script.javascript.js
├── .npmignore
├── README.md
├── package.json
└── umd-test.html
├── app-extension
├── src
│ ├── templates
│ │ └── .gitkeep
│ ├── boot
│ │ └── register.js
│ ├── install.js
│ ├── uninstall.js
│ └── index.js
├── .npmignore
├── package.json
└── README.md
├── jsconfig.json
├── .gitignore
├── .github
└── FUNDING.yml
├── README.md
└── LICENSE
/ui/src/mixins/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app-extension/src/templates/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/ui/src/components/Component.sass:
--------------------------------------------------------------------------------
1 | .QGoogleMap
2 | font-weight: bold
3 |
--------------------------------------------------------------------------------
/ui/build/entry/index.common.js:
--------------------------------------------------------------------------------
1 | import Plugin from '../../src/index'
2 |
3 | export default Plugin
4 |
--------------------------------------------------------------------------------
/ui/src/index.sass:
--------------------------------------------------------------------------------
1 | @import 'quasar/src/css/variables.sass'
2 | @import './components/Component.sass'
3 |
4 |
--------------------------------------------------------------------------------
/ui/build/entry/index.esm.js:
--------------------------------------------------------------------------------
1 | import Plugin from '../../src/index'
2 |
3 | export default Plugin
4 | export * from '../../src/index'
5 |
--------------------------------------------------------------------------------
/app-extension/src/boot/register.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import VuePlugin from 'quasar-ui-q-google-map'
3 |
4 | Vue.use(VuePlugin)
5 |
--------------------------------------------------------------------------------
/ui/build/entry/index.umd.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Plugin from '../../src/index'
3 |
4 | Vue.use(Plugin)
5 |
6 | export * from '../../src/index'
7 |
--------------------------------------------------------------------------------
/ui/build/script.open-umd.js:
--------------------------------------------------------------------------------
1 | const { resolve } = require('path')
2 | const open = require('open')
3 |
4 | open(
5 | resolve(__dirname, '../umd-test.html')
6 | )
7 |
--------------------------------------------------------------------------------
/ui/build/script.clean.js:
--------------------------------------------------------------------------------
1 | var
2 | rimraf = require('rimraf'),
3 | path = require('path')
4 |
5 | rimraf.sync(path.resolve(__dirname, '../dist/*'))
6 | console.log(` 💥 Cleaned build artifacts.\n`)
7 |
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 |
2 | {
3 | "compilerOptions": {
4 | // This must be specified if "paths" is set
5 | "baseUrl": ".",
6 | // Relative to "baseUrl"
7 | "paths": {
8 | "ui/*": ["src/*"]
9 | }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .thumbs.db
3 | node_modules
4 | dist
5 | yarn.lock
6 | npm-debug.log*
7 | yarn-debug.log*
8 | yarn-error.log*
9 | ui/dev
10 | # Editor directories and files
11 | .idea
12 | .vscode
13 | *.suo
14 | *.ntvs*
15 | *.njsproj
16 | *.sln
17 |
--------------------------------------------------------------------------------
/app-extension/.npmignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .thumbs.db
3 | yarn.lock
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 |
8 | # Editor directories and files
9 | .idea
10 | .vscode
11 | *.suo
12 | *.ntvs*
13 | *.njsproj
14 | *.sln
15 | .editorconfig
16 | .eslintignore
17 | .eslintrc.js
18 |
--------------------------------------------------------------------------------
/ui/src/components/Component.js:
--------------------------------------------------------------------------------
1 | import { QBadge } from 'quasar'
2 |
3 | export default {
4 | name: 'QGoogleMap',
5 |
6 | render (h) {
7 | return h(QBadge, {
8 | staticClass: 'QGoogleMap',
9 | props: {
10 | label: 'QGoogleMap'
11 | }
12 | })
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/ui/src/components/place-input.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
11 |
--------------------------------------------------------------------------------
/app-extension/src/install.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Quasar App Extension install script
3 | *
4 | * Docs: https://quasar.dev/app-extensions/development-guide/install-api
5 | * API: https://github.com/quasarframework/quasar/blob/master/app/lib/app-extension/InstallAPI.js
6 | */
7 |
8 | module.exports = function (api) {
9 | //
10 | }
11 |
--------------------------------------------------------------------------------
/ui/.npmignore:
--------------------------------------------------------------------------------
1 | /build
2 | /dev
3 | umd-test.html
4 |
5 | .DS_Store
6 | .thumbs.db
7 | yarn.lock
8 | npm-debug.log*
9 | yarn-debug.log*
10 | yarn-error.log*
11 |
12 | # Editor directories and files
13 | .idea
14 | .vscode
15 | *.suo
16 | *.ntvs*
17 | *.njsproj
18 | *.sln
19 | .editorconfig
20 | .eslintignore
21 | .eslintrc.js
22 |
--------------------------------------------------------------------------------
/app-extension/src/uninstall.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Quasar App Extension uninstall script
3 | *
4 | * Docs: https://quasar.dev/app-extensions/development-guide/uninstall-api
5 | * API: https://github.com/quasarframework/quasar/blob/master/app/lib/app-extension/UninstallAPI.js
6 | */
7 |
8 | module.exports = function (api) {
9 | //
10 | }
11 |
--------------------------------------------------------------------------------
/ui/src/components/QGoogleMap.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{map_data}}
4 |
5 |
6 |
7 |
13 |
14 |
17 |
--------------------------------------------------------------------------------
/ui/src/utils/bind-events.js:
--------------------------------------------------------------------------------
1 | export default (vueInst, googleMapsInst, events) => {
2 | for (const eventName of events) {
3 | if (vueInst.$gmapOptions.autobindAllEvents ||
4 | vueInst.$listeners[eventName]) {
5 | googleMapsInst.addListener(eventName, (ev) => {
6 | vueInst.$emit(eventName, ev)
7 | })
8 | }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/ui/build/config.js:
--------------------------------------------------------------------------------
1 | const { name, author, version } = require('../package.json')
2 | const year = (new Date()).getFullYear()
3 |
4 | module.exports = {
5 | name,
6 | version,
7 | banner:
8 | '/*!\n' +
9 | ' * ' + name + ' v' + version + '\n' +
10 | ' * (c) ' + year + ' ' + author + '\n' +
11 | ' * Released under the MIT License.\n' +
12 | ' */\n'
13 | }
14 |
--------------------------------------------------------------------------------
/ui/src/utils/lazy-value.js:
--------------------------------------------------------------------------------
1 | // This piece of code was orignally written by sindresorhus and can be seen here
2 | // https://github.com/sindresorhus/lazy-value/blob/master/index.js
3 |
4 | export default (fn) => {
5 | let called = false
6 | let ret
7 |
8 | return () => {
9 | if (!called) {
10 | called = true
11 | ret = fn()
12 | }
13 |
14 | return ret
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/ui/src/components/info-window.vue:
--------------------------------------------------------------------------------
1 | /* vim: set softtabstop=2 shiftwidth=2 expandtab : */
2 |
3 |
4 |
10 |
11 |
12 |
15 |
--------------------------------------------------------------------------------
/ui/src/components/autocomplete.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
11 |
--------------------------------------------------------------------------------
/ui/src/components/street-view-panorama.vue:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
11 |
12 |
22 |
--------------------------------------------------------------------------------
/ui/src/utils/mapped-props-to-vue-props.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Strips out the extraneous properties we have in our
3 | * props definitions
4 | * @param {Object} props
5 | */
6 | export default function (mappedProps) {
7 | return Object.entries(mappedProps)
8 | .map(([key, prop]) => {
9 | const value = {}
10 |
11 | if ('type' in prop) value.type = prop.type
12 | if ('default' in prop) value.default = prop.default
13 | if ('required' in prop) value.required = prop.required
14 |
15 | return [key, value]
16 | })
17 | .reduce((acc, [key, val]) => {
18 | acc[key] = val
19 | return acc
20 | }, {})
21 | }
22 |
--------------------------------------------------------------------------------
/ui/src/components/kml-layer.js:
--------------------------------------------------------------------------------
1 | import mapElementFactory from '../factories/map-element'
2 |
3 | const props = {
4 | url: {
5 | twoWay: false,
6 | type: String
7 | },
8 | map: {
9 | twoWay: true,
10 | type: Object
11 | }
12 | }
13 |
14 | const events = [
15 | 'click',
16 | 'rightclick',
17 | 'dblclick',
18 | 'mouseup',
19 | 'mousedown',
20 | 'mouseover',
21 | 'mouseout'
22 | ]
23 |
24 | /**
25 | * @class KML Layer
26 | *
27 | * KML Layer class (experimental)
28 | */
29 | export default mapElementFactory({
30 | mappedProps: props,
31 | events,
32 | name: 'kmlLayer',
33 | ctr: () => google.maps.KmlLayer
34 | })
35 |
--------------------------------------------------------------------------------
/ui/src/components/map.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
14 |
15 |
28 |
--------------------------------------------------------------------------------
/ui/build/index.js:
--------------------------------------------------------------------------------
1 | process.env.NODE_ENV = 'production'
2 |
3 | const parallel = require('os').cpus().length > 1
4 | const runJob = parallel ? require('child_process').fork : require
5 | const { join } = require('path')
6 | const { createFolder } = require('./utils')
7 | const { green, blue } = require('chalk')
8 |
9 | console.log()
10 |
11 | require('./script.app-ext.js').syncAppExt()
12 | require('./script.clean.js')
13 |
14 | console.log(` 📦 Building ${green('v' + require('../package.json').version)}...${parallel ? blue(' [multi-threaded]') : ''}\n`)
15 |
16 | createFolder('dist')
17 |
18 | runJob(join(__dirname, './script.javascript.js'))
19 | runJob(join(__dirname, './script.css.js'))
20 |
--------------------------------------------------------------------------------
/app-extension/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "quasar-app-extension-q-google-map",
3 | "version": "0.0.5",
4 | "description": "Extension to integrate Google Map into your Quasar project!",
5 | "author": "Mayur Patel",
6 | "license": "MIT",
7 | "main": "src/index.js",
8 | "repository": {
9 | "type": "git",
10 | "url": "https://github.com/mayur091193/q-google-map"
11 | },
12 | "bugs": "https://github.com/mayur091193/q-google-map/issues",
13 | "homepage": "https://github.com/mayur091193/q-google-map",
14 | "dependencies": {
15 | "quasar-ui-q-google-map": "0.0.5"
16 | },
17 | "engines": {
18 | "node": ">= 8.9.0",
19 | "npm": ">= 5.6.0",
20 | "yarn": ">= 1.6.0"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/ui/src/components/rectangle.js:
--------------------------------------------------------------------------------
1 | import mapElementFactory from '../factories/map-element'
2 |
3 | const props = {
4 | bounds: {
5 | type: Object,
6 | twoWay: true
7 | },
8 | draggable: {
9 | type: Boolean,
10 | default: false
11 | },
12 | editable: {
13 | type: Boolean,
14 | default: false
15 | },
16 | options: {
17 | type: Object,
18 | twoWay: false
19 | }
20 | }
21 |
22 | const events = [
23 | 'click',
24 | 'dblclick',
25 | 'drag',
26 | 'dragend',
27 | 'dragstart',
28 | 'mousedown',
29 | 'mousemove',
30 | 'mouseout',
31 | 'mouseover',
32 | 'mouseup',
33 | 'rightclick'
34 | ]
35 |
36 | export default mapElementFactory({
37 | mappedProps: props,
38 | name: 'rectangle',
39 | ctr: () => google.maps.Rectangle,
40 | events
41 | })
42 |
--------------------------------------------------------------------------------
/ui/src/utils/watch-primitive-properties.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Watch the individual properties of a PoD object, instead of the object
3 | * per se. This is different from a deep watch where both the reference
4 | * and the individual values are watched.
5 | *
6 | * In effect, it throttles the multiple $watch to execute at most once per tick.
7 | */
8 | export default function watchPrimitiveProperties (vueInst, propertiesToTrack, handler, immediate = false) {
9 | let isHandled = false
10 |
11 | function requestHandle () {
12 | if (!isHandled) {
13 | isHandled = true
14 | vueInst.$nextTick(() => {
15 | isHandled = false
16 | handler()
17 | })
18 | }
19 | }
20 |
21 | for (const prop of propertiesToTrack) {
22 | vueInst.$watch(prop, requestHandle, { immediate })
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: [mayur091193] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4 | patreon: # Replace with a single Patreon username
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | custom: ['https://www.buymeacoffee.com/mayur091193','https://paypal.me/mayurpp'] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
13 |
--------------------------------------------------------------------------------
/ui/src/components/circle.js:
--------------------------------------------------------------------------------
1 | import mapElementFactory from '../factories/map-element'
2 |
3 | const props = {
4 | center: {
5 | type: Object,
6 | twoWay: true,
7 | required: true
8 | },
9 | radius: {
10 | type: Number,
11 | twoWay: true
12 | },
13 | draggable: {
14 | type: Boolean,
15 | default: false
16 | },
17 | editable: {
18 | type: Boolean,
19 | default: false
20 | },
21 | options: {
22 | type: Object,
23 | twoWay: false
24 | }
25 | }
26 |
27 | const events = [
28 | 'click',
29 | 'dblclick',
30 | 'drag',
31 | 'dragend',
32 | 'dragstart',
33 | 'mousedown',
34 | 'mousemove',
35 | 'mouseout',
36 | 'mouseover',
37 | 'mouseup',
38 | 'rightclick'
39 | ]
40 |
41 | export default mapElementFactory({
42 | mappedProps: props,
43 | name: 'circle',
44 | ctr: () => google.maps.Circle,
45 | events
46 | })
47 |
--------------------------------------------------------------------------------
/ui/README.md:
--------------------------------------------------------------------------------
1 | # q-google-map
2 |
3 | q-google-map is a [Quasar App Extension](https://quasar.dev/app-extensions/introduction) to integrate google map in Quasar project.
4 |
5 | # Structure
6 | * [/ui](ui) - standalone npm package
7 | * [/app-extension](app-extension) - Quasar app extension
8 |
9 | # Support
10 |
11 | If this helped you in any way, you can contribute to this project by supporting me:
12 |
13 | ### [ Support my open-source work on GitHub](https://github.com/sponsors/mayur091193)
14 |
15 | ## Install
16 |
17 | To add this App Extension to your Quasar application, run the following (in your Quasar app folder):
18 |
19 | ```bash
20 | quasar ext add q-google-map
21 | ```
22 |
23 | # Uninstall
24 | To remove this App Extension from your Quasar application, run the following (in your Quasar app folder):
25 |
26 | ```
27 | quasar ext remove q-google-map
28 | ```
29 |
30 | ## [Docs and Demo](https://q-google-map.netlify.app)
31 |
--------------------------------------------------------------------------------
/app-extension/README.md:
--------------------------------------------------------------------------------
1 | # q-google-map
2 |
3 | q-google-map is a [Quasar App Extension](https://quasar.dev/app-extensions/introduction) to integrate google map in Quasar project.
4 |
5 | # Structure
6 | * [/ui](ui) - standalone npm package
7 | * [/app-extension](app-extension) - Quasar app extension
8 |
9 | # Support
10 |
11 | If this helped you in any way, you can contribute to this project by supporting me:
12 |
13 | ### [ Support my open-source work on GitHub](https://github.com/sponsors/mayur091193)
14 |
15 | ## Install
16 |
17 | To add this App Extension to your Quasar application, run the following (in your Quasar app folder):
18 |
19 | ```bash
20 | quasar ext add q-google-map
21 | ```
22 |
23 | # Uninstall
24 | To remove this App Extension from your Quasar application, run the following (in your Quasar app folder):
25 |
26 | ```
27 | quasar ext remove q-google-map
28 | ```
29 |
30 | ## [Docs and Demo](https://q-google-map.netlify.app)
31 |
--------------------------------------------------------------------------------
/ui/src/mixins/map-element.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @class MapElementMixin
3 | *
4 | * Extends components to include the following fields:
5 | *
6 | * @property $map The Google map (valid only after the promise returns)
7 | *
8 | *
9 | * */
10 | export default {
11 | inject: {
12 | $mapPromise: { default: 'abcdef' }
13 | },
14 |
15 | provide () {
16 | // Note: although this mixin is not "providing" anything,
17 | // components' expect the `$map` property to be present on the component.
18 | // In order for that to happen, this mixin must intercept the $mapPromise
19 | // .then(() =>) first before its component does so.
20 | //
21 | // Since a provide() on a mixin is executed before a provide() on the
22 | // component, putting this code in provide() ensures that the $map is
23 | // already set by the time the
24 | // component's provide() is called.
25 | this.$mapPromise.then((map) => {
26 | this.$map = map
27 | })
28 |
29 | return {}
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # [q-google-map](https://q-google-map.netlify.app)
2 |
3 | q-google-map is a [Quasar App Extension](https://quasar.dev/app-extensions/introduction) to integrate google map in Quasar project.
4 |
5 | # Structure
6 | * [/ui](ui) - standalone npm package
7 | * [/app-extension](app-extension) - [Quasar app extension](https://www.npmjs.com/package/quasar-app-extension-q-google-map)
8 |
9 | # Support
10 |
11 | If this helped you in any way, you can contribute to this project by supporting me:
12 |
13 | ### [ Support my open-source work on GitHub](https://github.com/sponsors/mayur091193)
14 |
15 | ## Install
16 |
17 | To add this App Extension to your Quasar application, run the following (in your Quasar app folder):
18 |
19 | ```bash
20 | quasar ext add q-google-map
21 | ```
22 |
23 | # Uninstall
24 | To remove this App Extension from your Quasar application, run the following (in your Quasar app folder):
25 |
26 | ```
27 | quasar ext remove q-google-map
28 | ```
29 |
30 | ## [Docs and Demo](https://q-google-map.netlify.app)
31 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) Mayur Patel
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/ui/src/utils/simulate-arrow-down.js:
--------------------------------------------------------------------------------
1 | // This piece of code was orignally written by amirnissim and can be seen here
2 | // http://stackoverflow.com/a/11703018/2694653
3 | // This has been ported to Vanilla.js by GuillaumeLeclerc
4 | export default (input) => {
5 | var _addEventListener = (input.addEventListener) ? input.addEventListener : input.attachEvent
6 |
7 | function addEventListenerWrapper (type, listener) {
8 | // Simulate a 'down arrow' keypress on hitting 'return' when no pac suggestion is selected,
9 | // and then trigger the original listener.
10 | if (type === 'keydown') {
11 | var origListener = listener
12 | listener = function (event) {
13 | var suggestionSelected = document.getElementsByClassName('pac-item-selected').length > 0
14 | if (event.which === 13 && !suggestionSelected) {
15 | var simulatedEvent = document.createEvent('Event')
16 | simulatedEvent.keyCode = 40
17 | simulatedEvent.which = 40
18 | origListener.apply(input, [simulatedEvent])
19 | }
20 | origListener.apply(input, [event])
21 | }
22 | }
23 | _addEventListener.apply(input, [type, listener])
24 | }
25 |
26 | input.addEventListener = addEventListenerWrapper
27 | input.attachEvent = addEventListenerWrapper
28 | }
29 |
--------------------------------------------------------------------------------
/app-extension/src/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Quasar App Extension index/runner script
3 | * (runs on each dev/build)
4 | *
5 | * Docs: https://quasar.dev/app-extensions/development-guide/index-api
6 | * API: https://github.com/quasarframework/quasar/blob/master/app/lib/app-extension/IndexAPI.js
7 | */
8 |
9 | function extendConf (conf) {
10 | // register our boot file
11 | conf.boot.push('~quasar-app-extension-q-google-map/src/boot/register.js')
12 |
13 | // make sure app extension files & ui package gets transpiled
14 | conf.build.transpileDependencies.push(/quasar-app-extension-q-google-map[\\/]src/)
15 |
16 | // make sure the stylesheet goes through webpack to avoid SSR issues
17 | conf.css.push('~quasar-ui-q-google-map/src/index.sass')
18 | }
19 |
20 | module.exports = function (api) {
21 | // Quasar compatibility check; you may need
22 | // hard dependencies, as in a minimum version of the "quasar"
23 | // package or a minimum version of "@quasar/app" CLI
24 | api.compatibleWith('quasar', '^1.1.1')
25 | api.compatibleWith('@quasar/app', '^1.1.0 || ^2.0.0')
26 |
27 | // Uncomment the line below if you provide a JSON API for your component
28 | // api.registerDescribeApi('QGoogleMap', '~quasar-ui-q-google-map/src/components/QGoogleMap.json')
29 |
30 | // We extend /quasar.conf.js
31 | api.extendQuasarConf(extendConf)
32 | }
33 |
--------------------------------------------------------------------------------
/ui/src/mixins/mountable.js:
--------------------------------------------------------------------------------
1 | /*
2 | Mixin for objects that are mounted by Google Maps
3 | Javascript API.
4 |
5 | These are objects that are sensitive to element resize
6 | operations so it exposes a property which accepts a bus
7 |
8 | */
9 |
10 | export default {
11 | props: ['resizeBus'],
12 |
13 | data () {
14 | return {
15 | _actualResizeBus: null
16 | }
17 | },
18 |
19 | created () {
20 | if (typeof this.resizeBus === 'undefined') {
21 | this.$data._actualResizeBus = this.$gmapDefaultResizeBus
22 | } else {
23 | this.$data._actualResizeBus = this.resizeBus
24 | }
25 | },
26 |
27 | methods: {
28 | _resizeCallback () {
29 | this.resize()
30 | },
31 | _delayedResizeCallback () {
32 | this.$nextTick(() => this._resizeCallback())
33 | }
34 | },
35 |
36 | watch: {
37 | resizeBus (newVal, oldVal) { // eslint-disable-line no-unused-vars
38 | this.$data._actualResizeBus = newVal
39 | },
40 | '$data._actualResizeBus' (newVal, oldVal) {
41 | if (oldVal) {
42 | oldVal.$off('resize', this._delayedResizeCallback)
43 | }
44 | if (newVal) {
45 | newVal.$on('resize', this._delayedResizeCallback)
46 | }
47 | }
48 | },
49 |
50 | destroyed () {
51 | if (this.$data._actualResizeBus) {
52 | this.$data._actualResizeBus.$off('resize', this._delayedResizeCallback)
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/ui/src/factories/promise-lazy.js:
--------------------------------------------------------------------------------
1 | import lazy from '../utils/lazy-value'
2 |
3 | export default function (loadGmapApi, GmapApi) {
4 | return function promiseLazyCreator (options) {
5 |
6 | // Things to do once the API is loaded
7 | function onApiLoaded () {
8 | GmapApi.gmapApi = {}
9 | return window.google
10 | }
11 |
12 | if (options.load) { // If library should load the API
13 | return lazy(() => { // Load the
14 |
15 | console.log("---------a")
16 | // This will only be evaluated once
17 | if (typeof window === 'undefined') { // server side -- never resolve this promise
18 |
19 | return new Promise(() => {}).then(onApiLoaded)
20 | } else {
21 |
22 | return new Promise((resolve, reject) => {
23 | try {
24 | window.vueGoogleMapsInit = resolve
25 | loadGmapApi(options.load, options.loadCn)
26 | } catch (err) {
27 | reject(err)
28 | }
29 | }).then(onApiLoaded)
30 | }
31 | })
32 | } else { // If library should not handle API, provide
33 | // end-users with the global `vueGoogleMapsInit: () => undefined`
34 | // when the Google Maps API has been loaded
35 | const promise = new Promise((resolve) => {
36 | if (typeof window === 'undefined') {
37 | // Do nothing if run from server-side
38 | return
39 | }
40 | window.vueGoogleMapsInit = resolve
41 | }).then(onApiLoaded)
42 |
43 | return lazy(() => promise)
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/ui/build/utils.js:
--------------------------------------------------------------------------------
1 | const
2 | fs = require('fs'),
3 | path = require('path'),
4 | zlib = require('zlib'),
5 | { green, blue, red, cyan } = require('chalk'),
6 | kebabRegex = /[A-Z\u00C0-\u00D6\u00D8-\u00DE]/g
7 |
8 | function getSize (code) {
9 | return (code.length / 1024).toFixed(2) + 'kb'
10 | }
11 |
12 | module.exports.createFolder = function (folder) {
13 | const dir = path.join(__dirname, '..', folder)
14 | if (!fs.existsSync(dir)) {
15 | fs.mkdirSync(dir)
16 | }
17 | }
18 |
19 | module.exports.writeFile = function (dest, code, zip) {
20 | const banner = dest.indexOf('.json') > -1
21 | ? red('[json]')
22 | : dest.indexOf('.js') > -1
23 | ? green('[js] ')
24 | : dest.indexOf('.ts') > -1
25 | ? cyan('[ts] ')
26 | : blue('[css] ')
27 |
28 | return new Promise((resolve, reject) => {
29 | function report (extra) {
30 | console.log(`${banner} ${path.relative(process.cwd(), dest).padEnd(41)} ${getSize(code).padStart(8)}${extra || ''}`)
31 | resolve(code)
32 | }
33 |
34 | fs.writeFile(dest, code, err => {
35 | if (err) return reject(err)
36 | if (zip) {
37 | zlib.gzip(code, (err, zipped) => {
38 | if (err) return reject(err)
39 | report(` (gzipped: ${getSize(zipped).padStart(8)})`)
40 | })
41 | }
42 | else {
43 | report()
44 | }
45 | })
46 | })
47 | }
48 |
49 | module.exports.readFile = function (file) {
50 | return fs.readFileSync(file, 'utf-8')
51 | }
52 |
53 | module.exports.logError = function (err) {
54 | console.error('\n' + red('[Error]'), err)
55 | console.log()
56 | }
57 |
--------------------------------------------------------------------------------
/ui/src/utils/two-way-binding-wrapper.js:
--------------------------------------------------------------------------------
1 | /**
2 | * When you have two-way bindings, but the actual bound value will not equal
3 | * the value you initially passed in, then to avoid an infinite loop you
4 | * need to increment a counter every time you pass in a value, decrement the
5 | * same counter every time the bound value changed, but only bubble up
6 | * the event when the counter is zero.
7 | *
8 | Example:
9 |
10 | Let's say DrawingRecognitionCanvas is a deep-learning backed canvas
11 | that, when given the name of an object (e.g. 'dog'), draws a dog.
12 | But whenever the drawing on it changes, it also sends back its interpretation
13 | of the image by way of the @newObjectRecognized event.
14 |
15 |
19 |
23 |
24 | new TwoWayBindingWrapper((increment, decrement, shouldUpdate) => {
25 | this.$watch('identifiedObject', () => {
26 | // new object passed in
27 | increment()
28 | })
29 | this.$deepLearningBackend.on('drawingChanged', () => {
30 | recognizeObject(this.$deepLearningBackend)
31 | .then((object) => {
32 | decrement()
33 | if (shouldUpdate()) {
34 | this.$emit('newObjectRecognized', object.name)
35 | }
36 | })
37 | })
38 | })
39 | */
40 | export default function twoWayBindingWrapper (fn) {
41 | let counter = 0
42 |
43 | fn(
44 | () => { counter += 1 },
45 | () => { counter = Math.max(0, counter - 1) },
46 | () => counter === 0
47 | )
48 | }
49 |
--------------------------------------------------------------------------------
/ui/src/components-implementation/info-window.js:
--------------------------------------------------------------------------------
1 | import mapElementFactory from '../factories/map-element'
2 |
3 | const props = {
4 | options: {
5 | type: Object,
6 | required: false,
7 | default () {
8 | return {}
9 | }
10 | },
11 | position: {
12 | type: Object,
13 | twoWay: true
14 | },
15 | zIndex: {
16 | type: Number,
17 | twoWay: true
18 | }
19 | }
20 |
21 | const events = [
22 | 'domready',
23 | 'closeclick',
24 | 'content_changed'
25 | ]
26 |
27 | export default mapElementFactory({
28 | mappedProps: props,
29 | events,
30 | name: 'infoWindow',
31 | ctr: () => google.maps.InfoWindow,
32 | props: {
33 | opened: {
34 | type: Boolean,
35 | default: true
36 | }
37 | },
38 |
39 | inject: {
40 | $markerPromise: {
41 | default: null
42 | }
43 | },
44 |
45 | mounted () {
46 | const el = this.$refs.flyaway
47 | el.parentNode.removeChild(el)
48 | },
49 |
50 | beforeCreate (options) {
51 | options.content = this.$refs.flyaway
52 |
53 | if (this.$markerPromise) {
54 | delete options.position
55 | return this.$markerPromise.then(mo => {
56 | this.$markerObject = mo
57 | return mo
58 | })
59 | }
60 | },
61 |
62 | methods: {
63 | _openInfoWindow () {
64 | if (this.opened) {
65 | if (this.$markerObject !== null) {
66 | this.$infoWindowObject.open(this.$map, this.$markerObject)
67 | } else {
68 | this.$infoWindowObject.open(this.$map)
69 | }
70 | } else {
71 | this.$infoWindowObject.close()
72 | }
73 | }
74 | },
75 |
76 | afterCreate () {
77 | this._openInfoWindow()
78 | this.$watch('opened', () => {
79 | this._openInfoWindow()
80 | })
81 | }
82 | })
83 |
--------------------------------------------------------------------------------
/ui/src/components/polyline.js:
--------------------------------------------------------------------------------
1 | import mapElementFactory from '../factories/map-element'
2 |
3 | const props = {
4 | draggable: {
5 | type: Boolean
6 | },
7 | editable: {
8 | type: Boolean
9 | },
10 | options: {
11 | twoWay: false,
12 | type: Object
13 | },
14 | path: {
15 | type: Array,
16 | twoWay: true
17 | }
18 | }
19 |
20 | const events = [
21 | 'click',
22 | 'dblclick',
23 | 'drag',
24 | 'dragend',
25 | 'dragstart',
26 | 'mousedown',
27 | 'mousemove',
28 | 'mouseout',
29 | 'mouseover',
30 | 'mouseup',
31 | 'rightclick'
32 | ]
33 |
34 | export default mapElementFactory({
35 | mappedProps: props,
36 | props: {
37 | deepWatch: {
38 | type: Boolean,
39 | default: false
40 | }
41 | },
42 | events,
43 |
44 | name: 'polyline',
45 | ctr: () => google.maps.Polyline,
46 |
47 | afterCreate (inst) {
48 | var clearEvents = () => {}
49 |
50 | this.$watch('path', (path) => {
51 | if (path) {
52 | clearEvents()
53 |
54 | this.$polylineObject.setPath(path)
55 |
56 | const mvcPath = this.$polylineObject.getPath()
57 | const eventListeners = []
58 |
59 | const updatePaths = () => {
60 | this.$emit('path_changed', this.$polylineObject.getPath())
61 | }
62 |
63 | eventListeners.push([mvcPath, mvcPath.addListener('insert_at', updatePaths)])
64 | eventListeners.push([mvcPath, mvcPath.addListener('remove_at', updatePaths)])
65 | eventListeners.push([mvcPath, mvcPath.addListener('set_at', updatePaths)])
66 |
67 | clearEvents = () => {
68 | eventListeners.map(([obj, listenerHandle]) => // eslint-disable-line no-unused-vars
69 | google.maps.event.removeListener(listenerHandle))
70 | }
71 | }
72 | }, {
73 | deep: this.deepWatch,
74 | immediate: true
75 | })
76 | }
77 | })
78 |
--------------------------------------------------------------------------------
/ui/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "quasar-ui-q-google-map",
3 | "version": "0.0.5",
4 | "author": "Mayur Patel",
5 | "description": "Extension to integrate Google Map into your Quasar project!",
6 | "license": "MIT",
7 | "module": "dist/index.esm.js",
8 | "main": "dist/index.common.js",
9 | "scripts": {
10 | "dev": "cd dev && yarn dev && cd ..",
11 | "dev:umd": "yarn build && node build/script.open-umd.js",
12 | "dev:ssr": "cd dev && yarn 'dev:ssr' && cd ..",
13 | "dev:ios": "cd dev && yarn 'dev:ios' && cd ..",
14 | "dev:android": "cd dev && yarn 'dev:android' && cd ..",
15 | "dev:electron": "cd dev && yarn 'dev:electron' && cd ..",
16 | "build": "node build/index.js",
17 | "build:js": "node build/script.javascript.js",
18 | "build:css": "node build/script.css.js"
19 | },
20 | "repository": {
21 | "type": "git",
22 | "url": ""
23 | },
24 | "bugs": "",
25 | "homepage": "",
26 | "devDependencies": {
27 | "@babel/core": "^7.11.5",
28 | "@babel/preset-env": "^7.11.5",
29 | "@lopatnov/rollup-plugin-uglify": "^2.1.0",
30 | "@rollup/plugin-buble": "^0.20.0",
31 | "@rollup/plugin-commonjs": "^11.1.0",
32 | "@rollup/plugin-json": "^4.0.0",
33 | "@rollup/plugin-node-resolve": "^8.4.0",
34 | "autoprefixer": "^9.6.1",
35 | "chalk": "^2.4.2",
36 | "cssnano": "^4.1.10",
37 | "fs-extra": "^8.1.0",
38 | "node-sass": "^7.0.0",
39 | "open": "^6.4.0",
40 | "postcss": "^7.0.18",
41 | "postcss-rtl": "^1.5.0",
42 | "quasar": "^1.0.0",
43 | "rimraf": "^3.0.0",
44 | "rollup": "^1.32.1",
45 | "rollup-plugin-vue": "^5.1.9",
46 | "terser": "^5.2.1",
47 | "uglify-es": "^3.3.9",
48 | "vue": "^2.6.11",
49 | "vue-template-compiler": "^2.6.11",
50 | "zlib": "^1.0.5"
51 | },
52 | "browserslist": [
53 | "last 1 version, not dead, ie >= 11"
54 | ],
55 | "dependencies": {}
56 | }
57 |
--------------------------------------------------------------------------------
/ui/build/script.app-ext.js:
--------------------------------------------------------------------------------
1 | const
2 | fs = require('fs'),
3 | path = require('path'),
4 | root = path.resolve(__dirname, '../..'),
5 | resolvePath = file => path.resolve(root, file),
6 | { blue } = require('chalk')
7 |
8 | const writeJson = function (file, json) {
9 | return fs.writeFileSync(file, JSON.stringify(json, null, 2) + '\n', 'utf-8')
10 | }
11 |
12 | module.exports.syncAppExt = function (both = true) {
13 | // make sure this project has an app-extension project
14 | const appExtDir = resolvePath('app-extension')
15 | if (!fs.existsSync(appExtDir)) {
16 | return
17 | }
18 |
19 | // make sure this project has an ui project
20 | const uiDir = resolvePath('ui')
21 | if (!fs.existsSync(uiDir)) {
22 | return
23 | }
24 |
25 | // get version and name from ui package.json
26 | const { name, version } = require(resolvePath(resolvePath('ui/package.json')))
27 |
28 | // read app-ext package.json
29 | const appExtFile = resolvePath('app-extension/package.json')
30 | let appExtJson = require(appExtFile),
31 | finished = false
32 |
33 | // sync version numbers
34 | if (both === true) {
35 | appExtJson.version = version
36 | }
37 |
38 | // check dependencies
39 | if (appExtJson.dependencies !== void 0) {
40 | if (appExtJson.dependencies[name] !== void 0) {
41 | appExtJson.dependencies[name] = '^' + version
42 | finished = true
43 | }
44 | }
45 | // check devDependencies, if not finished
46 | if (finished === false && appExtJson.devDependencies !== void 0) {
47 | if (appExtJson.devDependencies[name] !== void 0) {
48 | appExtJson.devDependencies[name] = '^' + version
49 | finished = true
50 | }
51 | }
52 |
53 | if (finished === true) {
54 | writeJson(appExtFile, appExtJson)
55 | console.log(` ⭐️ App Extension version ${blue(appExtJson.name)} synced with UI version.\n`)
56 | return
57 | }
58 |
59 | console.error(` App Extension version and dependency NOT synced.\n`)
60 | }
61 |
--------------------------------------------------------------------------------
/ui/build/script.css.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const sass = require('node-sass')
3 | const postcss = require('postcss')
4 | const cssnano = require('cssnano')
5 | const rtl = require('postcss-rtl')
6 | const autoprefixer = require('autoprefixer')
7 |
8 | const buildConf = require('./config')
9 | const buildUtils = require('./utils')
10 |
11 | const postCssCompiler = postcss([ autoprefixer ])
12 | const postCssRtlCompiler = postcss([ rtl({}) ])
13 |
14 | const nano = postcss([
15 | cssnano({
16 | preset: ['default', {
17 | mergeLonghand: false,
18 | convertValues: false,
19 | cssDeclarationSorter: false,
20 | reduceTransforms: false
21 | }]
22 | })
23 | ])
24 |
25 | Promise
26 | .all([
27 | generate('src/index.sass', `dist/index`)
28 | ])
29 | .catch(e => {
30 | console.error(e)
31 | process.exit(1)
32 | })
33 |
34 | /**
35 | * Helpers
36 | */
37 |
38 | function resolve (_path) {
39 | return path.resolve(__dirname, '..', _path)
40 | }
41 |
42 | function generate (src, dest) {
43 | src = resolve(src)
44 | dest = resolve(dest)
45 |
46 | return new Promise((resolve, reject) => {
47 | sass.render({ file: src, includePaths: ['node_modules'] }, (err, result) => {
48 | if (err) {
49 | reject(err)
50 | return
51 | }
52 |
53 | resolve(result.css)
54 | })
55 | })
56 | .then(code => buildConf.banner + code)
57 | .then(code => postCssCompiler.process(code, { from: void 0 }))
58 | .then(code => {
59 | code.warnings().forEach(warn => {
60 | console.warn(warn.toString())
61 | })
62 | return code.css
63 | })
64 | .then(code => Promise.all([
65 | generateUMD(dest, code),
66 | postCssRtlCompiler.process(code, { from: void 0 })
67 | .then(code => generateUMD(dest, code.css, '.rtl'))
68 | ]))
69 | }
70 |
71 | function generateUMD (dest, code, ext = '') {
72 | return buildUtils.writeFile(`${dest}${ext}.css`, code, true)
73 | .then(code => nano.process(code, { from: void 0 }))
74 | .then(code => buildUtils.writeFile(`${dest}${ext}.min.css`, code.css, true))
75 | }
76 |
--------------------------------------------------------------------------------
/ui/src/components-implementation/place-input.js:
--------------------------------------------------------------------------------
1 | import { bindProps, getPropsValues } from '../utils/bind-props'
2 | import downArrowSimulator from '../utils/simulate-arrow-down'
3 |
4 | const props = {
5 | bounds: {
6 | type: Object
7 | },
8 | defaultPlace: {
9 | type: String,
10 | default: ''
11 | },
12 | componentRestrictions: {
13 | type: Object,
14 | default: null
15 | },
16 | types: {
17 | type: Array,
18 | default: function () {
19 | return []
20 | }
21 | },
22 | placeholder: {
23 | required: false,
24 | type: String
25 | },
26 | className: {
27 | required: false,
28 | type: String
29 | },
30 | label: {
31 | required: false,
32 | type: String,
33 | default: null
34 | },
35 | selectFirstOnEnter: {
36 | require: false,
37 | type: Boolean,
38 | default: false
39 | }
40 | }
41 |
42 | export default {
43 | mounted () {
44 | const input = this.$refs.input
45 |
46 | // Allow default place to be set
47 | input.value = this.defaultPlace
48 | this.$watch('defaultPlace', () => {
49 | input.value = this.defaultPlace
50 | })
51 |
52 | this.$gmapApiPromiseLazy().then(() => {
53 | const options = getPropsValues(this, props)
54 | if (this.selectFirstOnEnter) {
55 | downArrowSimulator(this.$refs.input)
56 | }
57 |
58 | if (typeof (google.maps.places.Autocomplete) !== 'function') {
59 | throw new Error('google.maps.places.Autocomplete is undefined. Did you add \'places\' to libraries when loading Google Maps?')
60 | }
61 |
62 | this.autoCompleter = new google.maps.places.Autocomplete(this.$refs.input, options)
63 | const {placeholder, place, defaultPlace, className, label, selectFirstOnEnter, ...rest} = props // eslint-disable-line
64 | bindProps(this, this.autoCompleter, rest)
65 |
66 | this.autoCompleter.addListener('place_changed', () => {
67 | this.$emit('place_changed', this.autoCompleter.getPlace())
68 | })
69 | })
70 | },
71 | created () {
72 | console.warn('The PlaceInput class is deprecated! Please consider using the Autocomplete input instead') // eslint-disable-line no-console
73 | },
74 | props: props
75 | }
76 |
--------------------------------------------------------------------------------
/ui/umd-test.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | UMD test
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | quasar-ui-q-google-map v{{ version }}
22 |
23 |
24 | Quasar v{{ $q.version }}
25 |
26 |
27 |
28 |
29 |
30 |
31 | - In /ui, run: "yarn build"
32 | - You need to build & refresh page on each change manually.
33 | - Use self-closing tags only!
34 | - Example: <my-component></my-component>
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/ui/src/utils/bind-props.js:
--------------------------------------------------------------------------------
1 | import WatchPrimitiveProperties from './watch-primitive-properties'
2 |
3 | function capitalizeFirstLetter (string) {
4 | return string.charAt(0).toUpperCase() + string.slice(1)
5 | }
6 |
7 | export function getPropsValues (vueInst, props) {
8 | return Object.keys(props)
9 | .reduce(
10 | (acc, prop) => {
11 | if (vueInst[prop] !== undefined) {
12 | acc[prop] = vueInst[prop]
13 | }
14 | return acc
15 | },
16 | {}
17 | )
18 | }
19 |
20 | /**
21 | * Binds the properties defined in props to the google maps instance.
22 | * If the prop is an Object type, and we wish to track the properties
23 | * of the object (e.g. the lat and lng of a LatLng), then we do a deep
24 | * watch. For deep watch, we also prevent the _changed event from being
25 | * emitted if the data source was external.
26 | */
27 | export function bindProps (vueInst, googleMapsInst, props, options) {
28 | for (const attribute in props) {
29 | const { twoWay, type, trackProperties, noBind } = props[attribute]
30 |
31 | if (noBind) continue
32 |
33 | const setMethodName = 'set' + capitalizeFirstLetter(attribute)
34 | const getMethodName = 'get' + capitalizeFirstLetter(attribute)
35 | const eventName = attribute.toLowerCase() + '_changed'
36 | const initialValue = vueInst[attribute]
37 |
38 | if (typeof googleMapsInst[setMethodName] === 'undefined') {
39 | throw new Error(`${setMethodName} is not a method of (the Maps object corresponding to) ${vueInst.$options._componentTag}`)
40 | }
41 |
42 | // We need to avoid an endless
43 | // propChanged -> event emitted -> propChanged -> event emitted loop
44 | // although this may really be the user's responsibility
45 | if (type !== Object || !trackProperties) {
46 | // Track the object deeply
47 | vueInst.$watch(attribute, () => {
48 | const attributeValue = vueInst[attribute]
49 |
50 | googleMapsInst[setMethodName](attributeValue)
51 | }, {
52 | immediate: typeof initialValue !== 'undefined',
53 | deep: type === Object
54 | })
55 | } else {
56 | WatchPrimitiveProperties(
57 | vueInst,
58 | trackProperties.map(prop => `${attribute}.${prop}`),
59 | () => {
60 | googleMapsInst[setMethodName](vueInst[attribute])
61 | },
62 | vueInst[attribute] !== undefined
63 | )
64 | }
65 |
66 | if (twoWay &&
67 | (vueInst.$gmapOptions.autobindAllEvents ||
68 | vueInst.$listeners[eventName])) {
69 | googleMapsInst.addListener(eventName, (ev) => { // eslint-disable-line no-unused-vars
70 | vueInst.$emit(eventName, googleMapsInst[getMethodName]())
71 | })
72 | }
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/ui/src/init/initializer.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @param apiKey API Key, or object with the URL parameters. For example
3 | * to use Google Maps Premium API, pass
4 | * `{ client: }`.
5 | * You may pass the libraries and/or version (as `v`) parameter into
6 | * this parameter and skip the next two parameters
7 | * @param version Google Maps version
8 | * @param libraries Libraries to load (@see
9 | * https://developers.google.com/maps/documentation/javascript/libraries)
10 | * @param loadCn Boolean. If set to true, the map will be loaded from google maps China
11 | * (@see https://developers.google.com/maps/documentation/javascript/basics#GoogleMapsChina)
12 | *
13 | * Example:
14 | * ```
15 | * import {load} from 'vue-google-maps'
16 | *
17 | * load()
18 | *
19 | * load({
20 | * key: ,
21 | * })
22 | *
23 | * load({
24 | * client: ,
25 | * channel:
26 | * })
27 | * ```
28 | */
29 |
30 | export default (() => {
31 | let isApiSetUp = false
32 |
33 | return (options, loadCn) => {
34 |
35 | if (typeof document === 'undefined') {
36 | // Do nothing if run from server-side
37 | return
38 | }
39 |
40 | if (!isApiSetUp) {
41 | isApiSetUp = true
42 |
43 | const googleMapScript = document.createElement('SCRIPT')
44 |
45 | // Allow options to be an object.
46 | // This is to support more esoteric means of loading Google Maps,
47 | // such as Google for business
48 | // https://developers.google.com/maps/documentation/javascript/get-api-key#premium-auth
49 | if (typeof options !== 'object') {
50 | throw new Error('options should be an object')
51 | }
52 |
53 | // libraries
54 | if (Object.prototype.isPrototypeOf.call(Array.prototype, options.libraries)) {
55 | options.libraries = options.libraries.join(',')
56 | }
57 |
58 | options.callback = 'vueGoogleMapsInit'
59 |
60 | let baseUrl = 'https://maps.googleapis.com/'
61 |
62 | if (typeof loadCn === 'boolean' && loadCn === true) {
63 | baseUrl = 'https://maps.google.cn/'
64 | }
65 |
66 | const query = Object.keys(options)
67 | .map((key) => encodeURIComponent(key) + '=' + encodeURIComponent(options[key]))
68 | .join('&')
69 |
70 | const url = `${baseUrl}maps/api/js?${query}`
71 |
72 | googleMapScript.setAttribute('src', url)
73 | googleMapScript.setAttribute('async', '')
74 | googleMapScript.setAttribute('defer', '')
75 | document.head.appendChild(googleMapScript)
76 | } else {
77 | throw new Error('You already started the loading of google maps')
78 | }
79 | }
80 | })()
81 |
--------------------------------------------------------------------------------
/ui/src/components-implementation/autocomplete.js:
--------------------------------------------------------------------------------
1 | import { bindProps, getPropsValues } from '../utils/bind-props'
2 | import downArrowSimulator from '../utils/simulate-arrow-down'
3 | import mappedPropsToVueProps from '../utils/mapped-props-to-vue-props'
4 |
5 | const mappedProps = {
6 | bounds: {
7 | type: Object
8 | },
9 | componentRestrictions: {
10 | type: Object,
11 | // Do not bind -- must check for undefined
12 | // in the property
13 | noBind: true
14 | },
15 | types: {
16 | type: Array,
17 | default: function () {
18 | return []
19 | }
20 | }
21 | }
22 |
23 | const props = {
24 | selectFirstOnEnter: {
25 | required: false,
26 | type: Boolean,
27 | default: false
28 | },
29 | // the name of the ref to obtain the input (if its a child of component in the slot)
30 | childRefName: {
31 | required: false,
32 | type: String,
33 | default: 'input'
34 | },
35 | options: {
36 | type: Object
37 | },
38 | fields: {
39 | required: false,
40 | type: Array,
41 | default: null
42 | }
43 | }
44 |
45 | export default {
46 | mounted () {
47 | this.$gmapApiPromiseLazy().then(() => {
48 | var scopedInput = null
49 | if (this.$scopedSlots.input) {
50 | scopedInput = this.$scopedSlots.input()[0].context.$refs.input
51 | if (scopedInput && scopedInput.$refs) {
52 | scopedInput = scopedInput.$refs[this.childRefName || 'input']
53 | }
54 | if (scopedInput) { this.$refs.input = scopedInput }
55 | }
56 | if (this.selectFirstOnEnter) {
57 | downArrowSimulator(this.$refs.input)
58 | }
59 |
60 | if (typeof (google.maps.places.Autocomplete) !== 'function') {
61 | throw new Error('google.maps.places.Autocomplete is undefined. Did you add \'places\' to libraries when loading Google Maps?')
62 | }
63 |
64 | /* eslint-disable no-unused-vars */
65 | const finalOptions = {
66 | ...getPropsValues(this, mappedProps),
67 | ...this.options
68 | }
69 |
70 | this.$autocomplete = new google.maps.places.Autocomplete(this.$refs.input, finalOptions)
71 | bindProps(this, this.$autocomplete, mappedProps)
72 |
73 | this.$watch('componentRestrictions', v => {
74 | if (v !== undefined) {
75 | this.$autocomplete.setComponentRestrictions(v)
76 | }
77 | })
78 |
79 | // IMPORTANT: To avoid paying for data that you don't need,
80 | // be sure to use Autocomplete.setFields() to specify only the place data that you will use.
81 | if (this.fields) {
82 | this.$autocomplete.setFields(this.fields)
83 | }
84 |
85 | // Not using `bindEvents` because we also want
86 | // to return the result of `getPlace()`
87 | this.$autocomplete.addListener('place_changed', () => {
88 | this.$emit('place_changed', this.$autocomplete.getPlace())
89 | })
90 | })
91 | },
92 | props: {
93 | ...mappedPropsToVueProps(mappedProps),
94 | ...props
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/ui/src/components/marker.js:
--------------------------------------------------------------------------------
1 | import mapElementFactory from '../factories/map-element'
2 |
3 | const props = {
4 | animation: {
5 | twoWay: true,
6 | type: Number
7 | },
8 | attribution: {
9 | type: Object
10 | },
11 | clickable: {
12 | type: Boolean,
13 | twoWay: true,
14 | default: true
15 | },
16 | cursor: {
17 | type: String,
18 | twoWay: true
19 | },
20 | draggable: {
21 | type: Boolean,
22 | twoWay: true,
23 | default: false
24 | },
25 | icon: {
26 | twoWay: true
27 | },
28 | label: {
29 | },
30 | opacity: {
31 | type: Number,
32 | default: 1
33 | },
34 | options: {
35 | type: Object
36 | },
37 | place: {
38 | type: Object
39 | },
40 | position: {
41 | type: Object,
42 | twoWay: true
43 | },
44 | shape: {
45 | type: Object,
46 | twoWay: true
47 | },
48 | title: {
49 | type: String,
50 | twoWay: true
51 | },
52 | zIndex: {
53 | type: Number,
54 | twoWay: true
55 | },
56 | visible: {
57 | twoWay: true,
58 | default: true
59 | }
60 | }
61 |
62 | const events = [
63 | 'click',
64 | 'rightclick',
65 | 'dblclick',
66 | 'drag',
67 | 'dragstart',
68 | 'dragend',
69 | 'mouseup',
70 | 'mousedown',
71 | 'mouseover',
72 | 'mouseout'
73 | ]
74 |
75 | /**
76 | * @class Marker
77 | *
78 | * Marker class with extra support for
79 | *
80 | * - Embedded info windows
81 | * - Clustered markers
82 | *
83 | * Support for clustered markers is for backward-compatability
84 | * reasons. Otherwise we should use a cluster-marker mixin or
85 | * subclass.
86 | */
87 | export default mapElementFactory({
88 | mappedProps: props,
89 | events,
90 | name: 'marker',
91 | ctr: () => google.maps.Marker,
92 |
93 | inject: {
94 | $clusterPromise: {
95 | default: null
96 | }
97 | },
98 |
99 | render (h) {
100 | if (!this.$slots.default || this.$slots.default.length === 0) {
101 | return ''
102 | } else if (this.$slots.default.length === 1) { // So that infowindows can have a marker parent
103 | return this.$slots.default[0]
104 | } else {
105 | return h(
106 | 'div',
107 | this.$slots.default
108 | )
109 | }
110 | },
111 |
112 | destroyed () {
113 | if (!this.$markerObject) { return }
114 |
115 | if (this.$clusterObject) {
116 | // Repaint will be performed in `updated()` of cluster
117 | this.$clusterObject.removeMarker(this.$markerObject, true)
118 | } else {
119 | this.$markerObject.setMap(null)
120 | }
121 | },
122 |
123 | beforeCreate (options) {
124 | if (this.$clusterPromise) {
125 | options.map = null
126 | }
127 |
128 | return this.$clusterPromise
129 | },
130 |
131 | afterCreate (inst) {
132 | if (this.$clusterPromise) {
133 | this.$clusterPromise.then((co) => {
134 | co.addMarker(inst)
135 | this.$clusterObject = co
136 | })
137 | }
138 | }
139 | })
140 |
--------------------------------------------------------------------------------
/ui/src/components/cluster.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @class Cluster
3 | * @prop $clusterObject -- Exposes the marker clusterer to
4 | descendent Marker classes. Override this if you area
5 | extending the class
6 |
7 | List of properties from
8 | https://github.com/googlemaps/v3-utility-library/blob/master/markerclustererplus/src/markerclusterer.js
9 | **/
10 | import MarkerClusterer from 'marker-clusterer-plus'
11 | import mapElementFactory from '../factories/map-element'
12 |
13 | const props = {
14 | maxZoom: {
15 | type: Number,
16 | twoWay: false
17 | },
18 | batchSizeIE: {
19 | type: Number,
20 | twoWay: false
21 | },
22 | calculator: {
23 | type: Function,
24 | twoWay: false
25 | },
26 | enableRetinaIcons: {
27 | type: Boolean,
28 | twoWay: false
29 | },
30 | gridSize: {
31 | type: Number,
32 | twoWay: false
33 | },
34 | averageCenter: {
35 | type: Boolean,
36 | twoWay: false
37 | },
38 | ignoreHidden: {
39 | type: Boolean,
40 | twoWay: false
41 | },
42 | imageExtension: {
43 | type: String,
44 | twoWay: false
45 | },
46 | imagePath: {
47 | type: String,
48 | twoWay: false
49 | },
50 | imageSizes: {
51 | type: Array,
52 | twoWay: false
53 | },
54 | minimumClusterSize: {
55 | type: Number,
56 | twoWay: false
57 | },
58 | styles: {
59 | type: Array,
60 | twoWay: false
61 | },
62 | zoomOnClick: {
63 | type: Boolean,
64 | twoWay: false
65 | }
66 | }
67 |
68 | const events = [
69 | 'click',
70 | 'rightclick',
71 | 'dblclick',
72 | 'drag',
73 | 'dragstart',
74 | 'dragend',
75 | 'mouseup',
76 | 'mousedown',
77 | 'mouseover',
78 | 'mouseout'
79 | ]
80 |
81 | export default mapElementFactory({
82 | mappedProps: props,
83 | events,
84 | name: 'cluster',
85 | ctr: () => {
86 | if (typeof MarkerClusterer === 'undefined') {
87 | /* eslint-disable no-console */
88 | console.error('MarkerClusterer is not installed! require() it or include it from https://cdnjs.cloudflare.com/ajax/libs/js-marker-clusterer/1.0.0/markerclusterer.js')
89 | throw new Error('MarkerClusterer is not installed! require() it or include it from https://cdnjs.cloudflare.com/ajax/libs/js-marker-clusterer/1.0.0/markerclusterer.js')
90 | }
91 | return MarkerClusterer
92 | },
93 | ctrArgs: ({ map, ...otherOptions }) => [map, [], otherOptions],
94 |
95 | render (h) {
96 | //
97 | return h(
98 | 'div',
99 | this.$slots.default
100 | )
101 | },
102 |
103 | afterCreate (inst) {
104 | const reinsertMarkers = () => {
105 | const oldMarkers = inst.getMarkers()
106 | inst.clearMarkers()
107 | inst.addMarkers(oldMarkers)
108 | }
109 |
110 | for (const prop in props) {
111 | if (props[prop].twoWay) {
112 | this.$on(prop.toLowerCase() + '_changed', reinsertMarkers)
113 | }
114 | }
115 | },
116 |
117 | updated () {
118 | if (this.$clusterObject) {
119 | this.$clusterObject.repaint()
120 | }
121 | },
122 |
123 | beforeDestroy () {
124 | /* Performance optimization when destroying a large number of markers */
125 | this.$children.forEach(marker => {
126 | if (marker.$clusterObject === this.$clusterObject) {
127 | marker.$clusterObject = null
128 | }
129 | })
130 |
131 | if (this.$clusterObject) {
132 | this.$clusterObject.clearMarkers()
133 | }
134 | }
135 | })
136 |
--------------------------------------------------------------------------------
/ui/src/index.js:
--------------------------------------------------------------------------------
1 | import { version } from '../package.json'
2 | import loadGmapApi from './init/initializer'
3 | // import QGoogleMap from './components/QGoogleMap.vue'
4 | import promiseLazyFactory from './factories/promise-lazy'
5 |
6 | import KmlLayer from './components/kml-layer'
7 | import Marker from './components/marker'
8 | import Polyline from './components/polyline'
9 | import Polygon from './components/polygon'
10 | import Circle from './components/circle'
11 | import Rectangle from './components/rectangle'
12 |
13 | // Vue component imports
14 | import InfoWindow from './components/info-window.vue'
15 | import Map from './components/map.vue'
16 | import StreetViewPanorama from './components/street-view-panorama.vue'
17 | import PlaceInput from './components/place-input.vue'
18 | import Autocomplete from './components/autocomplete.vue'
19 |
20 | import MapElementMixin from './mixins/map-element'
21 | import MapElementFactory from './factories/map-element'
22 | import MountableMixin from './mixins/mountable'
23 |
24 | // HACK: Cluster should be loaded conditionally
25 | // However in the web version, it's not possible to write
26 | // `import 'vue2-google-maps/src/components/cluster'`, so we need to
27 | // import it anyway (but we don't have to register it)
28 | // Therefore we use babel-plugin-transform-inline-environment-variables to
29 | // set BUILD_DEV to truthy / falsy
30 | // const Cluster = (process.env.BUILD_DEV === '1')
31 | // ? undefined
32 | // : ((s) => s.default || s)(require('./components/cluster'))
33 |
34 | let GmapApi = null;
35 |
36 | export {
37 | loadGmapApi, KmlLayer, Marker, Polyline, Polygon, Circle, Rectangle,
38 | InfoWindow, Map, PlaceInput, MapElementMixin, MapElementFactory, Autocomplete,
39 | MountableMixin, StreetViewPanorama
40 | }
41 |
42 | export default {
43 | version,
44 | loadGmapApi,
45 |
46 | install (Vue, options) {
47 | options = JSON.parse(process.env.options);
48 | // Set defaults
49 | options = {
50 | installComponents: true,
51 | autobindAllEvents: false,
52 | ...options
53 | }
54 |
55 | // Update the global `GmapApi`. This will allow
56 | // components to use the `google` global reactively
57 | // via:
58 | // import {gmapApi} from 'vue2-google-maps'
59 | // export default { computed: { google: gmapApi } }
60 | GmapApi = new Vue({ data: { gmapApi: null } })
61 |
62 | const defaultResizeBus = new Vue()
63 |
64 | // Use a lazy to only load the API when
65 | // a VGM component is loaded
66 | const promiseLazyCreator = promiseLazyFactory(loadGmapApi, GmapApi)
67 | const gmapApiPromiseLazy = promiseLazyCreator(options)
68 |
69 | Vue.mixin({
70 | created () {
71 | this.$gmapDefaultResizeBus = defaultResizeBus
72 | this.$gmapOptions = options
73 | this.$gmapApiPromiseLazy = gmapApiPromiseLazy
74 | }
75 | })
76 |
77 | Vue.$gmapDefaultResizeBus = defaultResizeBus
78 | Vue.$gmapApiPromiseLazy = gmapApiPromiseLazy
79 |
80 | if (options.installComponents) {
81 | Vue.component('QGoogleMap', Map)
82 | Vue.component('QGoogleMapMarker', Marker)
83 | Vue.component('QGoogleMapPolygon', Polygon)
84 | Vue.component('QGoogleMapInfoWindow', InfoWindow)
85 | Vue.component('QGoogleMapPolyline', Polyline)
86 | Vue.component('QGoogleMapCircle', Circle)
87 | Vue.component('QGoogleMapRectangle', Rectangle)
88 | Vue.component('QGoogleMapKmlLayer', KmlLayer)
89 |
90 | Vue.component('QGoogleMapAutocomplete', Autocomplete)
91 | Vue.component('QGoogleMapPlaceInput', PlaceInput)
92 | Vue.component('QGoogleMapStreetViewPanorama', StreetViewPanorama)
93 | }
94 | //
95 | }
96 | }
97 |
98 | export function gmapApi () {
99 | return GmapApi.gmapApi && window.google
100 | }
101 |
102 |
103 |
--------------------------------------------------------------------------------
/ui/src/components/polygon.js:
--------------------------------------------------------------------------------
1 | import mapElementFactory from '../factories/map-element'
2 |
3 | const props = {
4 | draggable: {
5 | type: Boolean
6 | },
7 | editable: {
8 | type: Boolean
9 | },
10 | options: {
11 | type: Object
12 | },
13 | path: {
14 | type: Array,
15 | twoWay: true,
16 | noBind: true
17 | },
18 | paths: {
19 | type: Array,
20 | twoWay: true,
21 | noBind: true
22 | },
23 | geojson: {
24 | type: Object,
25 | twoWay: true,
26 | noBind: true
27 | }
28 | }
29 |
30 | const events = [
31 | 'click',
32 | 'dblclick',
33 | 'drag',
34 | 'dragend',
35 | 'dragstart',
36 | 'mousedown',
37 | 'mousemove',
38 | 'mouseout',
39 | 'mouseover',
40 | 'mouseup',
41 | 'rightclick'
42 | ]
43 |
44 | export default mapElementFactory({
45 | props: {
46 | deepWatch: {
47 | type: Boolean,
48 | default: false
49 | }
50 | },
51 | events,
52 | mappedProps: props,
53 | name: 'polygon',
54 | ctr: () => google.maps.Polygon,
55 |
56 | beforeCreate (options) {
57 | if (!options.path) delete options.path
58 | if (!options.paths) delete options.paths
59 | },
60 |
61 | afterCreate (inst) {
62 | var clearEvents = () => {}
63 |
64 | this.$watch('geojson', (geojson) => {
65 |
66 | if (geojson) {
67 | let a = inst;
68 | inst.map.data.addGeoJson(geojson);
69 | }
70 | }, {
71 | deep: this.deepWatch,
72 | immediate: true
73 | });
74 | // Watch paths, on our own, because we do not want to set either when it is
75 | // empty
76 | this.$watch('paths', (paths) => {
77 | if (paths) {
78 | clearEvents()
79 |
80 | inst.setPaths(paths)
81 |
82 | const updatePaths = () => {
83 | this.$emit('paths_changed', inst.getPaths())
84 | }
85 | const eventListeners = []
86 |
87 | const mvcArray = inst.getPaths()
88 | for (let i = 0; i < mvcArray.getLength(); i++) {
89 | const mvcPath = mvcArray.getAt(i)
90 | eventListeners.push([mvcPath, mvcPath.addListener('insert_at', updatePaths)])
91 | eventListeners.push([mvcPath, mvcPath.addListener('remove_at', updatePaths)])
92 | eventListeners.push([mvcPath, mvcPath.addListener('set_at', updatePaths)])
93 | }
94 | eventListeners.push([mvcArray, mvcArray.addListener('insert_at', updatePaths)])
95 | eventListeners.push([mvcArray, mvcArray.addListener('remove_at', updatePaths)])
96 | eventListeners.push([mvcArray, mvcArray.addListener('set_at', updatePaths)])
97 |
98 | clearEvents = () => {
99 | eventListeners.map(([obj, listenerHandle]) => // eslint-disable-line no-unused-vars
100 | google.maps.event.removeListener(listenerHandle))
101 | }
102 | }
103 | }, {
104 | deep: this.deepWatch,
105 | immediate: true
106 | })
107 |
108 | this.$watch('path', (path) => {
109 | if (path) {
110 | clearEvents()
111 |
112 | inst.setPaths(path)
113 |
114 | const mvcPath = inst.getPath()
115 | const eventListeners = []
116 |
117 | const updatePaths = () => {
118 | this.$emit('path_changed', inst.getPath())
119 | }
120 |
121 | eventListeners.push([mvcPath, mvcPath.addListener('insert_at', updatePaths)])
122 | eventListeners.push([mvcPath, mvcPath.addListener('remove_at', updatePaths)])
123 | eventListeners.push([mvcPath, mvcPath.addListener('set_at', updatePaths)])
124 |
125 | clearEvents = () => {
126 | eventListeners.map(([obj, listenerHandle]) => // eslint-disable-line no-unused-vars
127 | google.maps.event.removeListener(listenerHandle))
128 | }
129 | }
130 | }, {
131 | deep: this.deepWatch,
132 | immediate: true
133 | })
134 | }
135 | })
136 |
--------------------------------------------------------------------------------
/ui/src/components-implementation/street-view-panorama.js:
--------------------------------------------------------------------------------
1 | import bindEvents from '../utils/bind-events'
2 | import { bindProps, getPropsValues } from '../utils/bind-props'
3 | import mountableMixin from '../mixins/mountable'
4 |
5 | import twoWayBindingWrapper from '../utils/two-way-binding-wrapper'
6 | import watchPrimitiveProperties from '../utils/watch-primitive-properties'
7 | import mappedPropsToVueProps from '../utils/mapped-props-to-vue-props'
8 |
9 | const props = {
10 | zoom: {
11 | twoWay: true,
12 | type: Number
13 | },
14 | pov: {
15 | twoWay: true,
16 | type: Object,
17 | trackProperties: ['pitch', 'heading']
18 | },
19 | position: {
20 | twoWay: true,
21 | type: Object,
22 | noBind: true
23 | },
24 | pano: {
25 | twoWay: true,
26 | type: String
27 | },
28 | motionTracking: {
29 | twoWay: false,
30 | type: Boolean
31 | },
32 | visible: {
33 | twoWay: true,
34 | type: Boolean,
35 | default: true
36 | },
37 | options: {
38 | twoWay: false,
39 | type: Object,
40 | default () { return {} }
41 | }
42 | }
43 |
44 | const events = [
45 | 'closeclick',
46 | 'status_changed'
47 | ]
48 |
49 | export default {
50 | mixins: [mountableMixin],
51 | props: mappedPropsToVueProps(props),
52 | replace: false, // necessary for css styles
53 | methods: {
54 | resize () {
55 | if (this.$panoObject) {
56 | google.maps.event.trigger(this.$panoObject, 'resize')
57 | }
58 | }
59 | },
60 |
61 | provide () {
62 | const promise = new Promise((resolve, reject) => {
63 | this.$panoPromiseDeferred = { resolve, reject }
64 | })
65 | return {
66 | $panoPromise: promise,
67 | $mapPromise: promise // so that we can use it with markers
68 | }
69 | },
70 |
71 | computed: {
72 | finalLat () {
73 | return this.position &&
74 | (typeof this.position.lat === 'function') ? this.position.lat() : this.position.lat
75 | },
76 | finalLng () {
77 | return this.position &&
78 | (typeof this.position.lng === 'function') ? this.position.lng() : this.position.lng
79 | },
80 | finalLatLng () {
81 | return {
82 | lat: this.finalLat,
83 | lng: this.finalLng
84 | }
85 | }
86 | },
87 |
88 | watch: {
89 | zoom (zoom) {
90 | if (this.$panoObject) {
91 | this.$panoObject.setZoom(zoom)
92 | }
93 | }
94 | },
95 |
96 | mounted () {
97 | return this.$gmapApiPromiseLazy().then(() => {
98 | // getting the DOM element where to create the map
99 | const element = this.$refs['vue-street-view-pano']
100 |
101 | // creating the map
102 | const options = {
103 | ...this.options,
104 | ...getPropsValues(this, props)
105 | }
106 | delete options.options
107 |
108 | this.$panoObject = new google.maps.StreetViewPanorama(element, options)
109 |
110 | // binding properties (two and one way)
111 | bindProps(this, this.$panoObject, props)
112 | // binding events
113 | bindEvents(this, this.$panoObject, events)
114 |
115 | // manually trigger position
116 | twoWayBindingWrapper((increment, decrement, shouldUpdate) => {
117 | // Panos take a while to load
118 | increment()
119 |
120 | this.$panoObject.addListener('position_changed', () => {
121 | if (shouldUpdate()) {
122 | this.$emit('position_changed', this.$panoObject.getPosition())
123 | }
124 | decrement()
125 | })
126 |
127 | const updateCenter = () => {
128 | increment()
129 | this.$panoObject.setPosition(this.finalLatLng)
130 | }
131 |
132 | watchPrimitiveProperties(
133 | this,
134 | ['finalLat', 'finalLng'],
135 | updateCenter
136 | )
137 | })
138 |
139 | this.$panoPromiseDeferred.resolve(this.$panoObject)
140 |
141 | return this.$panoPromise
142 | }).catch((error) => {
143 | throw error
144 | })
145 | }
146 | }
147 |
--------------------------------------------------------------------------------
/ui/src/factories/map-element.js:
--------------------------------------------------------------------------------
1 | import bindEvents from '../utils/bind-events'
2 | import { bindProps, getPropsValues } from '../utils/bind-props'
3 | import MapElementMixin from '../mixins/map-element'
4 | import mappedPropsToVueProps from '../utils/mapped-props-to-vue-props'
5 |
6 | /**
7 | *
8 | * @param {Object} options
9 | * @param {Object} options.mappedProps - Definitions of props
10 | * @param {Object} options.mappedProps.PROP.type - Value type
11 | * @param {Boolean} options.mappedProps.PROP.twoWay
12 | * - Whether the prop has a corresponding PROP_changed
13 | * event
14 | * @param {Boolean} options.mappedProps.PROP.noBind
15 | * - If true, do not apply the default bindProps / bindEvents.
16 | * However it will still be added to the list of component props
17 | * @param {Object} options.props - Regular Vue-style props.
18 | * Note: must be in the Object form because it will be
19 | * merged with the `mappedProps`
20 | *
21 | * @param {Object} options.events - Google Maps API events
22 | * that are not bound to a corresponding prop
23 | * @param {String} options.name - e.g. `polyline`
24 | * @param {=> String} options.ctr - constructor, e.g.
25 | * `google.maps.Polyline`. However, since this is not
26 | * generally available during library load, this becomes
27 | * a function instead, e.g. () => google.maps.Polyline
28 | * which will be called only after the API has been loaded
29 | * @param {(MappedProps, OtherVueProps) => Array} options.ctrArgs -
30 | * If the constructor in `ctr` needs to be called with
31 | * arguments other than a single `options` object, e.g. for
32 | * GroundOverlay, we call `new GroundOverlay(url, bounds, options)`
33 | * then pass in a function that returns the argument list as an array
34 | *
35 | * Otherwise, the constructor will be called with an `options` object,
36 | * with property and values merged from:
37 | *
38 | * 1. the `options` property, if any
39 | * 2. a `map` property with the Google Maps
40 | * 3. all the properties passed to the component in `mappedProps`
41 | * @param {Object => Any} options.beforeCreate -
42 | * Hook to modify the options passed to the initializer
43 | * @param {(options.ctr, Object) => Any} options.afterCreate -
44 | * Hook called when
45 | *
46 | */
47 |
48 | /**
49 | * Custom assert for local validation
50 | **/
51 | function _assert (v, message) {
52 | if (!v) throw new Error(message)
53 | }
54 |
55 | export default function (options) {
56 | const {
57 | mappedProps,
58 | name,
59 | ctr,
60 | ctrArgs,
61 | events,
62 | beforeCreate,
63 | afterCreate,
64 | props,
65 | ...rest
66 | } = options
67 |
68 | const promiseName = `$${name}Promise`
69 | const instanceName = `$${name}Object`
70 |
71 | _assert(!(rest.props instanceof Array), '`props` should be an object, not Array')
72 |
73 | return {
74 | ...(typeof GENERATE_DOC !== 'undefined' ? { $vgmOptions: options } : {}),
75 | mixins: [MapElementMixin],
76 | props: {
77 | ...props,
78 | ...mappedPropsToVueProps(mappedProps)
79 | },
80 | render () { return '' },
81 | provide () {
82 | const promise = this.$mapPromise.then((map) => {
83 | // Infowindow needs this to be immediately available
84 | this.$map = map
85 |
86 | // Initialize the maps with the given options
87 | const initialOptions = {
88 | ...this.options,
89 | map,
90 | ...getPropsValues(this, mappedProps)
91 | }
92 | // don't use delete keyword in order to create a more predictable code for the engine
93 | let { options, ...finalOptions } = initialOptions // delete the extra options
94 | options = finalOptions
95 |
96 | if (beforeCreate) {
97 | const result = beforeCreate.bind(this)(options)
98 |
99 | if (result instanceof Promise) {
100 | return result.then(() => ({ options }))
101 | }
102 | }
103 | return { options }
104 | }).then(({ options }) => {
105 | const ConstructorObject = ctr()
106 | // https://stackoverflow.com/questions/1606797/use-of-apply-with-new-operator-is-this-possible
107 | this[instanceName] = ctrArgs
108 | ? new (Function.prototype.bind.call(
109 | ConstructorObject,
110 | null,
111 | ...ctrArgs(options, getPropsValues(this, props || {}))
112 | ))()
113 | : new ConstructorObject(options)
114 |
115 | bindProps(this, this[instanceName], mappedProps)
116 | bindEvents(this, this[instanceName], events)
117 |
118 | if (afterCreate) {
119 | afterCreate.bind(this)(this[instanceName])
120 | }
121 | return this[instanceName]
122 | })
123 |
124 | this[promiseName] = promise
125 | return { [promiseName]: promise }
126 | },
127 | destroyed () {
128 | // Note: not all Google Maps components support maps
129 | if (this[instanceName] && this[instanceName].setMap) {
130 | this[instanceName].setMap(null)
131 | }
132 | },
133 | ...rest
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/ui/build/script.javascript.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const fs = require('fs')
3 | const fse = require('fs-extra')
4 | const rollup = require('rollup')
5 | const uglify = require('uglify-es')
6 | const buble = require('@rollup/plugin-buble')
7 | const json = require('@rollup/plugin-json')
8 | const { nodeResolve } = require('@rollup/plugin-node-resolve')
9 | const VuePlugin = require('rollup-plugin-vue')
10 | const commonjs = require('@rollup/plugin-commonjs')
11 |
12 | const buildConf = require('./config')
13 | const buildUtils = require('./utils')
14 |
15 | const rollupPlugins = [
16 | commonjs(),
17 | nodeResolve({
18 | extensions: ['.js'],
19 | preferBuiltins: false
20 | }),
21 | json(),
22 | VuePlugin(),
23 | buble({
24 | objectAssign: 'Object.assign',
25 | }),
26 | ]
27 |
28 | const builds = [
29 | {
30 | rollup: {
31 | input: {
32 | input: pathResolve('entry/index.esm.js')
33 | },
34 | output: {
35 | file: pathResolve('../dist/index.esm.js'),
36 | format: 'es'
37 | }
38 | },
39 | build: {
40 | // unminified: true,
41 | minified: true
42 | }
43 | },
44 | {
45 | rollup: {
46 | input: {
47 | input: pathResolve('entry/index.common.js')
48 | },
49 | output: {
50 | file: pathResolve('../dist/index.common.js'),
51 | format: 'cjs'
52 | }
53 | },
54 | build: {
55 | // unminified: true,
56 | minified: true
57 | }
58 | },
59 | {
60 | rollup: {
61 | input: {
62 | input: pathResolve('entry/index.umd.js')
63 | },
64 | output: {
65 | name: 'qGoogleMap',
66 | file: pathResolve('../dist/index.umd.js'),
67 | format: 'umd'
68 | }
69 | },
70 | build: {
71 | unminified: true,
72 | minified: true,
73 | minExt: true
74 | }
75 | }
76 | ]
77 |
78 | // Add your asset folders here, if needed
79 | // addAssets(builds, 'icon-set', 'iconSet')
80 | // addAssets(builds, 'lang', 'lang')
81 |
82 | build(builds)
83 |
84 | /**
85 | * Helpers
86 | */
87 |
88 | function pathResolve (_path) {
89 | return path.resolve(__dirname, _path)
90 | }
91 |
92 | // eslint-disable-next-line no-unused-vars
93 | function addAssets (builds, type, injectName) {
94 | const
95 | files = fs.readdirSync(pathResolve('../../ui/src/components/' + type)),
96 | plugins = [ buble(bubleConfig) ],
97 | outputDir = pathResolve(`../dist/${type}`)
98 |
99 | fse.mkdirp(outputDir)
100 |
101 | files
102 | .filter(file => file.endsWith('.js'))
103 | .forEach(file => {
104 | const name = file.substr(0, file.length - 3).replace(/-([a-z])/g, g => g[1].toUpperCase())
105 | builds.push({
106 | rollup: {
107 | input: {
108 | input: pathResolve(`../src/components/${type}/${file}`),
109 | plugins
110 | },
111 | output: {
112 | file: addExtension(pathResolve(`../dist/${type}/${file}`), 'umd'),
113 | format: 'umd',
114 | name: `qGoogleMap.${injectName}.${name}`
115 | }
116 | },
117 | build: {
118 | minified: true
119 | }
120 | })
121 | })
122 | }
123 |
124 | function build (builds) {
125 | return Promise
126 | .all(builds.map(genConfig).map(buildEntry))
127 | .catch(buildUtils.logError)
128 | }
129 |
130 | function genConfig (opts) {
131 | Object.assign(opts.rollup.input, {
132 | plugins: rollupPlugins,
133 | external: [ 'vue', 'quasar' ]
134 | })
135 |
136 | Object.assign(opts.rollup.output, {
137 | banner: buildConf.banner,
138 | globals: { vue: 'Vue', quasar: 'Quasar' }
139 | })
140 |
141 | return opts
142 | }
143 |
144 | function addExtension (filename, ext = 'min') {
145 | const insertionPoint = filename.lastIndexOf('.')
146 | return `${filename.slice(0, insertionPoint)}.${ext}${filename.slice(insertionPoint)}`
147 | }
148 |
149 | function buildEntry (config) {
150 | return rollup
151 | .rollup(config.rollup.input)
152 | .then(bundle => bundle.generate(config.rollup.output))
153 | .then(({ output }) => {
154 | const code = config.rollup.output.format === 'umd'
155 | ? injectVueRequirement(output[0].code)
156 | : output[0].code
157 |
158 | return config.build.unminified
159 | ? buildUtils.writeFile(config.rollup.output.file, code)
160 | : code
161 | })
162 | .then(code => {
163 | if (!config.build.minified) {
164 | return code
165 | }
166 |
167 | const minified = uglify.minify(code, {
168 | compress: {
169 | pure_funcs: ['makeMap']
170 | }
171 | })
172 |
173 | if (minified.error) {
174 | return Promise.reject(minified.error)
175 | }
176 |
177 | return buildUtils.writeFile(
178 | config.build.minExt === true
179 | ? addExtension(config.rollup.output.file)
180 | : config.rollup.output.file,
181 | buildConf.banner + minified.code,
182 | true
183 | )
184 | })
185 | .catch(err => {
186 | console.error(err)
187 | process.exit(1)
188 | })
189 | }
190 |
191 | function injectVueRequirement (code) {
192 | // eslint-disable-next-line
193 | const index = code.indexOf(`Vue = Vue && Vue.hasOwnProperty('default') ? Vue['default'] : Vue`)
194 |
195 | if (index === -1) {
196 | return code
197 | }
198 |
199 | const checkMe = ` if (Vue === void 0) {
200 | console.error('[ Quasar ] Vue is required to run. Please add a script tag for it before loading Quasar.')
201 | return
202 | }
203 | `
204 |
205 | return code.substring(0, index - 1) +
206 | checkMe +
207 | code.substring(index)
208 | }
209 |
--------------------------------------------------------------------------------
/ui/src/components-implementation/map.js:
--------------------------------------------------------------------------------
1 | import bindEvents from '../utils/bind-events'
2 | import { bindProps, getPropsValues } from '../utils/bind-props'
3 | import mountableMixin from '../mixins/mountable'
4 |
5 | import twoWayBindingWrapper from '../utils/two-way-binding-wrapper'
6 | import watchPrimitiveProperties from '../utils/watch-primitive-properties'
7 | import mappedPropsToVueProps from '../utils/mapped-props-to-vue-props'
8 |
9 | const props = {
10 | center: {
11 | required: true,
12 | twoWay: true,
13 | type: Object,
14 | noBind: true
15 | },
16 | zoom: {
17 | required: false,
18 | twoWay: true,
19 | type: Number,
20 | noBind: true
21 | },
22 | heading: {
23 | type: Number,
24 | twoWay: true
25 | },
26 | mapTypeId: {
27 | twoWay: true,
28 | type: String
29 | },
30 | tilt: {
31 | twoWay: true,
32 | type: Number
33 | },
34 | options: {
35 | type: Object,
36 | default () { return {} }
37 | }
38 | }
39 |
40 | const events = [
41 | 'bounds_changed',
42 | 'click',
43 | 'dblclick',
44 | 'drag',
45 | 'dragend',
46 | 'dragstart',
47 | 'idle',
48 | 'mousemove',
49 | 'mouseout',
50 | 'mouseover',
51 | 'resize',
52 | 'rightclick',
53 | 'tilesloaded'
54 | ]
55 |
56 | // Plain Google Maps methods exposed here for convenience
57 | const linkedMethods = [
58 | 'panBy',
59 | 'panTo',
60 | 'panToBounds',
61 | 'fitBounds'
62 | ].reduce((all, methodName) => {
63 | all[methodName] = function (...args) {
64 | if (this.$mapObject) { this.$mapObject[methodName].apply(this.$mapObject, args) }
65 | }
66 | return all
67 | }, {})
68 |
69 | // Other convenience methods exposed by Vue Google Maps
70 | const customMethods = {
71 | resize () {
72 | if (this.$mapObject) {
73 | google.maps.event.trigger(this.$mapObject, 'resize')
74 | }
75 | },
76 | resizePreserveCenter () {
77 | if (!this.$mapObject) { return }
78 |
79 | const oldCenter = this.$mapObject.getCenter()
80 | google.maps.event.trigger(this.$mapObject, 'resize')
81 | this.$mapObject.setCenter(oldCenter)
82 | },
83 |
84 | /// Override mountableMixin::_resizeCallback
85 | /// because resizePreserveCenter is usually the
86 | /// expected behaviour
87 | _resizeCallback () {
88 | this.resizePreserveCenter()
89 | }
90 | }
91 |
92 | const recyclePrefix = '__gmc__'
93 |
94 | export default {
95 | mixins: [mountableMixin],
96 | props: mappedPropsToVueProps(props),
97 |
98 | provide () {
99 | this.$mapPromise = new Promise((resolve, reject) => {
100 | this.$mapPromiseDeferred = { resolve, reject }
101 | })
102 | return {
103 | $mapPromise: this.$mapPromise
104 | }
105 | },
106 |
107 | computed: {
108 | finalLat () {
109 | return this.center &&
110 | (typeof this.center.lat === 'function') ? this.center.lat() : this.center.lat
111 | },
112 | finalLng () {
113 | return this.center &&
114 | (typeof this.center.lng === 'function') ? this.center.lng() : this.center.lng
115 | },
116 | finalLatLng () {
117 | return { lat: this.finalLat, lng: this.finalLng }
118 | }
119 | },
120 |
121 | watch: {
122 | zoom (zoom) {
123 | if (this.$mapObject) {
124 | this.$mapObject.setZoom(zoom)
125 | }
126 | }
127 | },
128 |
129 | beforeDestroy () {
130 | const recycleKey = this.getRecycleKey()
131 | if (window[recycleKey]) {
132 | window[recycleKey].div = this.$mapObject.getDiv()
133 | }
134 | },
135 |
136 | mounted () {
137 | return this.$gmapApiPromiseLazy().then(() => {
138 | // getting the DOM element where to create the map
139 | const element = this.$refs['vue-map']
140 |
141 | // creating the map
142 | const initialOptions = {
143 | ...this.options,
144 | ...getPropsValues(this, props)
145 | }
146 |
147 | // don't use delete keyword in order to create a more predictable code for the engine
148 | let { options, ...finalOptions } = initialOptions
149 | options = finalOptions
150 |
151 | const recycleKey = this.getRecycleKey()
152 | if (this.options.recycle && window[recycleKey]) {
153 | element.appendChild(window[recycleKey].div)
154 | this.$mapObject = window[recycleKey].map
155 | this.$mapObject.setOptions(options)
156 | } else {
157 | // console.warn('[vue2-google-maps] New google map created')
158 | this.$mapObject = new google.maps.Map(element, options)
159 | window[recycleKey] = { map: this.$mapObject }
160 | }
161 |
162 | // binding properties (two and one way)
163 | bindProps(this, this.$mapObject, props)
164 | // binding events
165 | bindEvents(this, this.$mapObject, events)
166 |
167 | // manually trigger center and zoom
168 | twoWayBindingWrapper((increment, decrement, shouldUpdate) => {
169 | this.$mapObject.addListener('center_changed', () => {
170 | if (shouldUpdate()) {
171 | this.$emit('center_changed', this.$mapObject.getCenter())
172 | }
173 | decrement()
174 | })
175 |
176 | const updateCenter = () => {
177 | increment()
178 | this.$mapObject.setCenter(this.finalLatLng)
179 | }
180 |
181 | watchPrimitiveProperties(
182 | this,
183 | ['finalLat', 'finalLng'],
184 | updateCenter
185 | )
186 | })
187 | this.$mapObject.addListener('zoom_changed', () => {
188 | this.$emit('zoom_changed', this.$mapObject.getZoom())
189 | })
190 | this.$mapObject.addListener('bounds_changed', () => {
191 | this.$emit('bounds_changed', this.$mapObject.getBounds())
192 | })
193 |
194 | this.$mapPromiseDeferred.resolve(this.$mapObject)
195 |
196 | return this.$mapObject
197 | }).catch((error) => {
198 | throw error
199 | })
200 | },
201 | methods: {
202 | ...customMethods,
203 | ...linkedMethods,
204 | getRecycleKey () {
205 | return this.options.recycle ? recyclePrefix + this.options.recycle : recyclePrefix
206 | }
207 | }
208 | }
209 |
--------------------------------------------------------------------------------