├── .github └── FUNDING.yml ├── .gitignore ├── LICENSE ├── README.md ├── app-extension ├── .npmignore ├── README.md ├── package.json └── src │ ├── boot │ └── register.js │ ├── index.js │ ├── install.js │ ├── templates │ └── .gitkeep │ └── uninstall.js ├── jsconfig.json └── ui ├── .npmignore ├── README.md ├── build ├── config.js ├── entry │ ├── index.common.js │ ├── index.esm.js │ └── index.umd.js ├── index.js ├── script.app-ext.js ├── script.clean.js ├── script.css.js ├── script.javascript.js ├── script.open-umd.js └── utils.js ├── package-lock.json ├── package-lock_old.json ├── package.json ├── src ├── components-implementation │ ├── autocomplete.js │ ├── info-window.js │ ├── map.js │ ├── place-input.js │ └── street-view-panorama.js ├── components │ ├── Component.js │ ├── Component.sass │ ├── QGoogleMap.vue │ ├── autocomplete.vue │ ├── circle.js │ ├── cluster.js │ ├── info-window.vue │ ├── kml-layer.js │ ├── map.vue │ ├── marker.js │ ├── place-input.vue │ ├── polygon.js │ ├── polyline.js │ ├── rectangle.js │ └── street-view-panorama.vue ├── factories │ ├── map-element.js │ └── promise-lazy.js ├── index.js ├── index.sass ├── init │ └── initializer.js ├── mixins │ ├── .gitkeep │ ├── map-element.js │ └── mountable.js └── utils │ ├── bind-events.js │ ├── bind-props.js │ ├── lazy-value.js │ ├── mapped-props-to-vue-props.js │ ├── simulate-arrow-down.js │ ├── two-way-binding-wrapper.js │ └── watch-primitive-properties.js └── umd-test.html /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /app-extension/src/templates/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mayur091193/q-google-map/d6bdaa54426e07fd828934e61d75e54aa9b24625/app-extension/src/templates/.gitkeep -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/build/entry/index.common.js: -------------------------------------------------------------------------------- 1 | import Plugin from '../../src/index' 2 | 3 | export default Plugin 4 | -------------------------------------------------------------------------------- /ui/build/entry/index.esm.js: -------------------------------------------------------------------------------- 1 | import Plugin from '../../src/index' 2 | 3 | export default Plugin 4 | export * from '../../src/index' 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/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 | -------------------------------------------------------------------------------- /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.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 | -------------------------------------------------------------------------------- /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/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/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/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/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/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-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-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 | -------------------------------------------------------------------------------- /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/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/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/Component.sass: -------------------------------------------------------------------------------- 1 | .QGoogleMap 2 | font-weight: bold 3 | -------------------------------------------------------------------------------- /ui/src/components/QGoogleMap.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 13 | 14 | 17 | -------------------------------------------------------------------------------- /ui/src/components/autocomplete.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 11 | -------------------------------------------------------------------------------- /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/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/components/info-window.vue: -------------------------------------------------------------------------------- 1 | /* vim: set softtabstop=2 shiftwidth=2 expandtab : */ 2 | 3 | 11 | 12 | 15 | -------------------------------------------------------------------------------- /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 | 10 | 11 | 14 | 15 | 28 | -------------------------------------------------------------------------------- /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/place-input.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 11 | -------------------------------------------------------------------------------- /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/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/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/components/street-view-panorama.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 11 | 12 | 22 | -------------------------------------------------------------------------------- /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/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/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/index.sass: -------------------------------------------------------------------------------- 1 | @import 'quasar/src/css/variables.sass' 2 | @import './components/Component.sass' 3 | 4 | -------------------------------------------------------------------------------- /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/mixins/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mayur091193/q-google-map/d6bdaa54426e07fd828934e61d75e54aa9b24625/ui/src/mixins/.gitkeep -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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/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/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/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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------