Update dependencies and improve build system

This commit is contained in:
Fawad Mirzad
2021-02-13 17:30:46 +01:00
parent 04431e1b39
commit 9a20c42459
57 changed files with 8395 additions and 1064 deletions

3
.browserslistrc Normal file
View File

@@ -0,0 +1,3 @@
> 1%
last 2 versions
not dead

9
.editorconfig Normal file
View File

@@ -0,0 +1,9 @@
root = true
[*]
charset=utf-8
end_of_line=lf
insert_final_newline=true
indent_style=space
indent_size=2
trim_trailing_whitespace = true

1
.env Normal file
View File

@@ -0,0 +1 @@
VUE_APP_TITLE=Vue3 Mobile

1
.env.development Normal file
View File

@@ -0,0 +1 @@
VUE_APP_API_BASE_URL=https://development.example.com

1
.env.production Normal file
View File

@@ -0,0 +1 @@
VUE_APP_API_BASE_URL=https://production.example.com

View File

@@ -1,30 +1,42 @@
module.exports = { module.exports = {
root: true, env: {
env: { browser: true,
node: true es6: true,
}, node: true,
extends: [ },
"plugin:vue/vue3-essential", "globals": {
"eslint:recommended", "google": true
"@vue/prettier", },
"plugin:cypress/recommended" // the ts-eslint recommended ruleset sets the parser so we need to set it back
], parser: 'vue-eslint-parser',
parserOptions: {
parser: "babel-eslint" parserOptions: {
}, ecmaVersion: 2020,
rules: { extraFileExtensions: ['.vue'],
"no-console": process.env.NODE_ENV === "production" ? "warn" : "off", ecmaFeatures: {
"no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off" jsx: true,
}, },
overrides: [ sourceType: 'module',
{ },
files: [
"**/__tests__/*.{j,t}s?(x)", extends: [
"**/tests/unit/**/*.spec.{j,t}s?(x)" 'plugin:vue/vue3-essential',
], 'eslint:recommended',
env: { 'plugin:prettier/recommended',
jest: true 'prettier/vue',
} ],
}
] rules: {
}; 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
},
overrides: [
{
files: ['*.js'],
rules: {
'@typescript-eslint/no-var-requires': 'off',
},
},
],
}

27
.gitignore vendored
View File

@@ -1,3 +1,26 @@
.idea .DS_Store
node_modules node_modules
dist /dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
# Git
*.diff
*.patch
*.bak

5
.prettierrc.js Normal file
View File

@@ -0,0 +1,5 @@
module.exports = {
printWidth: 100,
semi: false,
singleQuote: true,
}

15
.stylelintrc.js Normal file
View File

@@ -0,0 +1,15 @@
module.exports = {
extends: 'stylelint-config-standard',
plugins: ['stylelint-scss', 'stylelint-order'],
ignoreFiles: ['node_modules/**', 'src/assets/font/**', 'src/assets/style/reset.css'],
rules: {
'at-rule-no-unknown': [
true,
{
ignoreAtRules: ['extends', 'ignores', 'include', 'mixin', 'if', 'else', 'media', 'for'],
},
],
'order/order': ['custom-properties', 'declarations'],
'order/properties-order': ['width', 'height'],
},
}

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 Jamie Yang
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

16
babel.config.js Normal file
View File

@@ -0,0 +1,16 @@
module.exports = {
presets: [
[
'@babel/preset-env',
{
useBuiltIns: 'usage', // adds specific imports for polyfills when they are used in each file.
modules: false, // preserve ES modules.
corejs: { version: 3, proposals: true }, // enable polyfilling of every proposal supported by core-js.
},
],
],
plugins: [
'@babel/plugin-transform-runtime', // enables the re-use of Babel's injected helper code to save on codesize.
],
exclude: [/core-js/],
}

11
jsconfig.json Normal file
View File

@@ -0,0 +1,11 @@
{
"compilerOptions": {
"target": "ESNext",
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
},
"exclude": ["node_modules", "dist"],
"include": ["src/**/*"]
}

6793
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,87 +1,60 @@
{ {
"name": "@fawmi/vue-google-maps", "name": "@fawmi/vue-google-maps",
"description": "Google Map components for Vue.js 3", "description": "Google Map components for Vue.js 3",
"version": "0.3.0", "version": "0.5.0",
"private": false, "private": false,
"main": "src/index.js", "main": "src/index.js",
"browserify": {
"transform": [
"babelify",
"vueify"
]
},
"bugs": { "bugs": {
"url": "https://github.com/xkjyeah/vue-google-maps/issues" "url": "https://github.com/xkjyeah/vue-google-maps/issues"
}, },
"bundleDependencies": false, "scripts": {
"build": "node service/commands/build.js",
"lint": "eslint --ext .js,.vue src"
},
"dependencies": { "dependencies": {
"babel-runtime": "^6.26.0", "@babel/runtime": "^7.12.13",
"marker-clusterer-plus": "^2.1.4" "core-js": "^3.8.3",
"vue": "^3.0.5"
}, },
"deprecated": false,
"devDependencies": { "devDependencies": {
"babel-cli": "^6.26.0", "@babel/core": "^7.12.13",
"babel-core": "^6.26.0", "@babel/plugin-transform-runtime": "^7.12.15",
"babel-loader": "^7.1.3", "@babel/preset-env": "^7.12.13",
"babel-plugin-minify-dead-code-elimination": "^0.4.0", "@vue/compiler-sfc": "^3.0.5",
"babel-plugin-transform-inline-environment-variables": "^0.4.0", "babel-loader": "^8.2.2",
"babel-plugin-transform-object-rest-spread": "^6.26.0", "chalk": "^4.1.0",
"babel-preset-env": "^1.6.1", "copy-webpack-plugin": "^7.0.0",
"babel-preset-es2015": "^6.24.1", "css-loader": "^5.0.1",
"babel-preset-stage-2": "^6.24.1", "dotenv": "^8.2.0",
"code": "^5.2.0", "dotenv-expand": "^5.1.0",
"cross-env": "^1.0.8", "eslint": "^7.19.0",
"css-loader": "^0.23.0", "eslint-config-prettier": "^7.2.0",
"eslint": "^3.17.1", "eslint-formatter-friendly": "^7.0.0",
"eslint-config-standard": "^7.0.1", "eslint-plugin-prettier": "^3.3.1",
"eslint-plugin-html": "^2.0.1", "eslint-plugin-vue": "^7.5.0",
"eslint-plugin-promise": "^3.5.0", "eslint-webpack-plugin": "^2.5.0",
"eslint-plugin-standard": "^2.1.1", "html-webpack-plugin": "^5.0.0",
"file-loader": "^0.8.4", "mini-css-extract-plugin": "^1.3.5",
"gh-pages": "^0.11.0", "ora": "^5.3.0",
"jsdom": "^9.8.3", "postcss": "^8.2.4",
"json-loader": "^0.5.7", "postcss-loader": "^5.0.0",
"lab": "^15.3.0", "prettier": "2.2.1",
"less": "^2.5.3", "rimraf": "^3.0.2",
"less-loader": "^2.2.2", "strip-ansi": "^6.0.0",
"lodash": "^4.15.0", "style-loader": "^2.0.0",
"lodash-es": "^4.17.4", "terser-webpack-plugin": "^5.1.1",
"lodash-webpack-plugin": "^0.11.4", "thread-loader": "^3.0.1",
"node-sass": "^4.9.4", "vue-eslint-parser": "^7.4.1",
"puppeteer": "^1.1.0", "vue-loader": "^16.1.2",
"raw-loader": "^0.5.1", "vue-style-loader": "^4.1.2",
"sass-loader": "^7.0.1", "webpack": "^5.20.2",
"shx": "^0.2.0", "webpack-merge": "^5.7.3"
"style-loader": "^0.13.0",
"stylus-loader": "^1.4.0",
"template-html-loader": "0.0.3",
"vue": "^2.5.0",
"vue-hot-reload-api": "^1.2.0",
"vue-html-loader": "^1.0.0",
"vue-loader": "^14.2.2",
"vue-router": "^2.7.0",
"vue-template-compiler": "^2.1.6",
"webpack": "^4.28.3",
"webpack-cli": "^3.2.0",
"yaml-loader": "^0.5.0"
}, },
"bundleDependencies": false,
"homepage": "https://vue-map.netlify.app/", "homepage": "https://vue-map.netlify.app/",
"license": "MIT", "license": "MIT",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/fawmi/vue-google-maps.git" "url": "https://github.com/fawmi/vue-google-maps.git"
},
"scripts": {
"build": "npm run build-babel && npm run build-webpack",
"build-babel": "shx mkdir -p dist && shx rm -r dist && cross-env BUILD_DEV=1 babel src -D --out-dir dist && echo {} > dist/.babelrc",
"build-examples": "npm run build-examples-before && npm run build-examples-webpack",
"build-examples-before": "npm run build && shx cp dist/vue-google-maps.js examples",
"build-examples-webpack": "cd examples && cross-env NODE_ENV=production webpack",
"build-webpack": "cross-env NODE_ENV=production webpack --progress --hide-modules",
"deploy": "npm run build-examples && gh-pages -d examples",
"lab-tests": "lab -T test/test-setup/babel-transform.js -l -S test",
"lint": "eslint --ext .vue,.js src && eslint --ext .vue,.html,.js test",
"prepare": "npm run build",
"test": "npm run lab-tests && npm run lint"
} }
} }

40
service/commands/build.js Normal file
View File

@@ -0,0 +1,40 @@
'use strict'
const loadEnv = require('../utils/loadEnv')
loadEnv()
loadEnv('production')
const rm = require('rimraf')
const webpack = require('webpack')
const { error, done } = require('../utils/logger')
const paths = require('../utils/paths')
const webpackConfig = require('../config/prod')
const config = require('../project.config')
rm(paths.resolve(config.outputDir), (err) => {
if (err) throw err
webpack(webpackConfig, (err, stats) => {
if (err) throw err
process.stdout.write(
stats.toString({
colors: true,
modules: false,
children: false,
chunks: false,
chunkModules: false,
}) + '\n\n'
)
if (stats.hasErrors()) {
error('Build failed with errors.\n')
process.exit(1)
}
done('Build complete.\n')
})
})

60
service/config/base.js Normal file
View File

@@ -0,0 +1,60 @@
'use strict'
const { DefinePlugin } = require('webpack')
const { VueLoaderPlugin } = require('vue-loader')
const ESLintPlugin = require('eslint-webpack-plugin')
const resolveClientEnv = require('../utils/resolveClientEnv')
const paths = require('../utils/paths')
const config = require('../project.config')
const isProd = process.env.NODE_ENV === 'production'
const outputFileName = paths.getAssetPath(`js/[name]${isProd ? '' : ''}.js`)
module.exports = {
context: process.cwd(),
entry: {
app: './src/main.js',
},
output: {
path: paths.resolve(config.outputDir),
publicPath: config.dev.publicPath,
filename: outputFileName,
chunkFilename: outputFileName,
},
resolve: {
alias: {
'@': paths.resolve('src'),
},
extensions: ['.js','.vue', '.json'],
},
plugins: [
new ESLintPlugin({
emitError: true,
emitWarning: true,
extensions: ['.js', '.vue'],
formatter: require('eslint-formatter-friendly'),
}),
new VueLoaderPlugin(),
new DefinePlugin({
__VUE_OPTIONS_API__: 'true',
__VUE_PROD_DEVTOOLS__: 'false',
...resolveClientEnv({ publicPath: config.dev.publicPath }),
}),
],
module: {
noParse: /^(vue)$/,
rules: [
{
test: /\.vue$/,
loader: 'vue-loader',
},
],
},
}

52
service/config/css.js Normal file
View File

@@ -0,0 +1,52 @@
'use strict'
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const genStyleRules = () => {
const isProd = process.env.NODE_ENV === 'production'
const cssLoader = {
loader: 'css-loader',
options: {
// how many loaders before css-loader should be applied to [@import]ed resources.
// stylePostLoader injected by vue-loader + postcss-loader
importLoaders: 1 + 1,
esModule: false, // css-loader using ES Modules as default in v4, but vue-style-loader support cjs only.
},
}
const postcssLoader = {
loader: 'postcss-loader'
}
const extractPluginLoader = {
loader: MiniCssExtractPlugin.loader,
}
const vueStyleLoader = {
loader: 'vue-style-loader',
}
function createCSSRule(test, loader, loaderOptions) {
const loaders = [cssLoader, postcssLoader]
if (isProd) {
loaders.unshift(extractPluginLoader)
} else {
loaders.unshift(vueStyleLoader)
}
if (loader) {
loaders.push({ loader, options: loaderOptions })
}
return { test, use: loaders }
}
return [
createCSSRule(/\.css$/),
]
}
module.exports = {
module: {
rules: genStyleRules(),
},
}

34
service/config/dev.js Normal file
View File

@@ -0,0 +1,34 @@
'use strict'
const { merge } = require('webpack-merge')
const baseWebpackConfig = require('./base')
const cssWebpackConfig = require('./css')
const config = require('../project.config')
module.exports = merge(baseWebpackConfig, cssWebpackConfig, {
mode: 'development',
devtool: 'eval-cheap-module-source-map',
devServer: {
historyApiFallback: {
rewrites: [{ from: /./, to: '/index.html' }],
},
dev: {
publicPath: config.dev.publicPath,
},
overlay: {
warnings: true,
errors: true,
},
open: false,
host: '0.0.0.0',
port: config.dev.port,
liveReload: false,
},
infrastructureLogging: {
level: 'warn',
},
})

40
service/config/prod.js Normal file
View File

@@ -0,0 +1,40 @@
'use strict'
const { merge } = require('webpack-merge')
const TerserPlugin = require('terser-webpack-plugin')
const baseWebpackConfig = require('./base')
const cssWebpackConfig = require('./css')
const config = require('../project.config')
const terserOptions = require('./terserOptions')
module.exports = merge(baseWebpackConfig, cssWebpackConfig, {
mode: 'production',
output: {
publicPath: config.build.publicPath,
},
optimization: {
minimize: true,
minimizer: [new TerserPlugin(terserOptions())],
moduleIds: 'deterministic',
splitChunks: {
cacheGroups: {
defaultVendors: {
name: `chunk-vendors`,
test: /[\\/]node_modules[\\/]/,
priority: -10,
chunks: 'initial',
},
common: {
name: `chunk-common`,
minChunks: 2,
priority: -20,
chunks: 'initial',
reuseExistingChunk: true,
},
},
},
},
})

View File

@@ -0,0 +1,42 @@
'use strict'
module.exports = (options) => ({
terserOptions: {
compress: {
// turn off flags with small gains to speed up minification
arrows: false,
collapse_vars: false, // 0.3kb
comparisons: false,
computed_props: false,
hoist_funs: false,
hoist_props: false,
hoist_vars: false,
inline: false,
loops: false,
negate_iife: false,
properties: false,
reduce_funcs: false,
reduce_vars: false,
switches: false,
toplevel: false,
typeofs: false,
// a few flags with noticable gains/speed ratio
// numbers based on out of the box vendor bundle
booleans: true, // 0.7kb
if_return: true, // 0.4kb
sequences: true, // 0.7kb
unused: true, // 2.3kb
// required features to drop conditional branches
conditionals: true,
dead_code: true,
evaluate: true,
},
mangle: {
safari10: true,
},
},
// parallel: options.parallel,
extractComments: false,
})

14
service/project.config.js Normal file
View File

@@ -0,0 +1,14 @@
'use strict'
module.exports = {
outputDir: 'dist',
dev: {
publicPath: '/',
port: 8080,
},
build: {
publicPath: './',
},
}

View File

@@ -0,0 +1,17 @@
'use strict'
const os = require('os')
module.exports = function getLocalIP() {
const interfaces = os.networkInterfaces()
for (const devName in interfaces) {
const iface = interfaces[devName]
for (let i = 0; i < iface.length; i++) {
const alias = iface[i]
if (alias.family === 'IPv4' && alias.address !== '127.0.0.1' && !alias.internal) {
return alias.address
}
}
}
}

39
service/utils/loadEnv.js Normal file
View File

@@ -0,0 +1,39 @@
'use strict'
const path = require('path')
const dotenv = require('dotenv')
const dotenvExpand = require('dotenv-expand')
const { error } = require('./logger')
module.exports = function loadEnv(mode) {
const basePath = path.resolve(process.cwd(), `.env${mode ? `.${mode}` : ``}`)
const localPath = `${basePath}.local`
const load = (envPath) => {
try {
const env = dotenv.config({ path: envPath, debug: process.env.DEBUG })
dotenvExpand(env)
} catch (err) {
// only ignore error if file is not found
if (err.toString().indexOf('ENOENT') < 0) {
error(err)
}
}
}
load(localPath)
load(basePath)
// by default, NODE_ENV and BABEL_ENV are set to "development" unless mode
// is production or test. However the value in .env files will take higher
// priority.
if (mode) {
const defaultNodeEnv = mode === 'production' || mode === 'test' ? mode : 'development'
if (process.env.NODE_ENV == null) {
process.env.NODE_ENV = defaultNodeEnv
}
if (process.env.BABEL_ENV == null) {
process.env.BABEL_ENV = defaultNodeEnv
}
}
}

72
service/utils/logger.js Normal file
View File

@@ -0,0 +1,72 @@
'use strict'
const chalk = require('chalk')
const stripAnsi = require('strip-ansi')
const readline = require('readline')
const EventEmitter = require('events')
exports.events = new EventEmitter()
function _log(type, tag, message) {
if (process.env.VUE_CLI_API_MODE && message) {
exports.events.emit('log', {
message,
type,
tag,
})
}
}
const format = (label, msg) => {
return msg
.split('\n')
.map((line, i) => {
return i === 0 ? `${label} ${line}` : line.padStart(stripAnsi(label).length)
})
.join('\n')
}
const chalkTag = (msg) => chalk.bgBlackBright.white.dim(` ${msg} `)
exports.log = (msg = '', tag = null) => {
tag ? console.log(format(chalkTag(tag), msg)) : console.log(msg)
_log('log', tag, msg)
}
exports.info = (msg, tag = null) => {
console.log(format(chalk.bgBlue.black(' INFO ') + (tag ? chalkTag(tag) : ''), msg))
_log('info', tag, msg)
}
exports.done = (msg, tag = null) => {
console.log(format(chalk.bgGreen.black(' DONE ') + (tag ? chalkTag(tag) : ''), msg))
_log('done', tag, msg)
}
exports.warn = (msg, tag = null) => {
console.warn(
format(chalk.bgYellow.black(' WARN ') + (tag ? chalkTag(tag) : ''), chalk.yellow(msg))
)
_log('warn', tag, msg)
}
exports.error = (msg, tag = null) => {
console.error(format(chalk.bgRed(' ERROR ') + (tag ? chalkTag(tag) : ''), chalk.red(msg)))
_log('error', tag, msg)
if (msg instanceof Error) {
console.error(msg.stack)
_log('error', tag, msg.stack)
}
}
exports.clearConsole = (title) => {
if (process.stdout.isTTY) {
const blank = '\n'.repeat(process.stdout.rows)
console.log(blank)
readline.cursorTo(process.stdout, 0, 0)
readline.clearScreenDown(process.stdout)
if (title) {
console.log(title)
}
}
}

9
service/utils/paths.js Normal file
View File

@@ -0,0 +1,9 @@
'use strict'
const path = require('path')
// gen static file path
exports.getAssetPath = (...args) => path.posix.join('static', ...args)
// gen absolute path
exports.resolve = (...args) => path.posix.join(process.cwd(), ...args)

View File

@@ -0,0 +1,22 @@
const prefixRE = /^VUE_APP_/
module.exports = function resolveClientEnv(options, raw) {
const env = {}
Object.keys(process.env).forEach((key) => {
if (prefixRE.test(key) || key === 'NODE_ENV') {
env[key] = process.env[key]
}
})
env.PUBLIC_PATH = options.publicPath
if (raw) {
return env
}
for (const key in env) {
env[key] = JSON.stringify(env[key])
}
return {
'process.env': env,
}
}

57
service/utils/spinner.js Normal file
View File

@@ -0,0 +1,57 @@
'use strict'
const ora = require('ora')
const chalk = require('chalk')
const spinner = ora()
let lastMsg = null
let isPaused = false
exports.logWithSpinner = (symbol, msg) => {
if (!msg) {
msg = symbol
symbol = chalk.green('✔')
}
if (lastMsg) {
spinner.stopAndPersist({
symbol: lastMsg.symbol,
text: lastMsg.text,
})
}
spinner.text = ' ' + msg
lastMsg = {
symbol: symbol + ' ',
text: msg,
}
spinner.start()
}
exports.stopSpinner = (persist) => {
if (lastMsg && persist !== false) {
spinner.stopAndPersist({
symbol: lastMsg.symbol,
text: lastMsg.text,
})
} else {
spinner.stop()
}
lastMsg = null
}
exports.pauseSpinner = () => {
if (spinner.isSpinning) {
spinner.stop()
isPaused = true
}
}
exports.resumeSpinner = () => {
if (isPaused) {
spinner.start()
isPaused = false
}
}
exports.failSpinner = (text) => {
spinner.fail(text)
}

View File

@@ -1,19 +1,7 @@
{ {
"presets": [ "presets": ["@babel/preset-env"],
[
"env",
{
"targets": {
"browsers": [
"last 2 versions",
"safari >= 7"
]
}
}
]
],
"plugins": [ "plugins": [
"transform-object-rest-spread", "@babel/plugin-proposal-object-rest-spread",
"transform-inline-environment-variables", "transform-inline-environment-variables",
"minify-dead-code-elimination" "minify-dead-code-elimination"
] ]

View File

View File

@@ -1,11 +1,79 @@
<template> <template>
<input <input ref="input" v-bind="$attrs" v-on="$attrs" />
ref="input"
v-bind="$attrs"
v-on="$attrs"
/>
</template> </template>
<script> <script>
export default (function (x) { return x.default || x })(require('./autocompleteImpl.js')) import { bindProps, getPropsValues } from '../utils/bindProps.js'
import downArrowSimulator from '../utils/simulateArrowDown.js'
import { mappedPropsToVueProps } from './mapElementFactory'
const mappedProps = {
bounds: {
type: Object,
},
componentRestrictions: {
type: Object,
// Do not bind -- must check for undefined
// in the property
noBind: true,
},
types: {
type: Array,
default: function () {
return []
},
},
}
const props = {
selectFirstOnEnter: {
required: false,
type: Boolean,
default: false,
},
options: {
type: Object,
},
}
export default {
mounted() {
this.$gmapApiPromiseLazy().then(() => {
if (this.selectFirstOnEnter) {
downArrowSimulator(this.$refs.input)
}
if (typeof google.maps.places.Autocomplete !== 'function') {
throw new Error(
"google.maps.places.Autocomplete is undefined. Did you add 'places' to libraries when loading Google Maps?"
)
}
/* eslint-disable no-unused-vars */
const finalOptions = {
...getPropsValues(this, mappedProps),
...this.options,
}
this.$autocomplete = new google.maps.places.Autocomplete(this.$refs.input, finalOptions)
bindProps(this, this.$autocomplete, mappedProps)
this.$watch('componentRestrictions', (v) => {
if (v !== undefined) {
this.$autocomplete.setComponentRestrictions(v)
}
})
// Not using `bindEvents` because we also want
// to return the result of `getPlace()`
this.$autocomplete.addListener('place_changed', () => {
this.$emit('place_changed', this.$autocomplete.getPlace())
})
})
},
props: {
...mappedPropsToVueProps(mappedProps),
...props,
},
}
</script> </script>

View File

@@ -1,10 +1,10 @@
import {bindProps, getPropsValues} from '../utils/bindProps.js' import { bindProps, getPropsValues } from '../utils/bindProps.js'
import downArrowSimulator from '../utils/simulateArrowDown.js' import downArrowSimulator from '../utils/simulateArrowDown.js'
import {mappedPropsToVueProps} from './mapElementFactory' import { mappedPropsToVueProps } from './mapElementFactory'
const mappedProps = { const mappedProps = {
bounds: { bounds: {
type: Object type: Object,
}, },
componentRestrictions: { componentRestrictions: {
type: Object, type: Object,
@@ -16,7 +16,7 @@ const mappedProps = {
type: Array, type: Array,
default: function () { default: function () {
return [] return []
} },
}, },
} }
@@ -24,34 +24,36 @@ const props = {
selectFirstOnEnter: { selectFirstOnEnter: {
required: false, required: false,
type: Boolean, type: Boolean,
default: false default: false,
}, },
options: { options: {
type: Object type: Object,
} },
} }
export default { export default {
mounted () { mounted() {
this.$gmapApiPromiseLazy().then(() => { this.$gmapApiPromiseLazy().then(() => {
if (this.selectFirstOnEnter) { if (this.selectFirstOnEnter) {
downArrowSimulator(this.$refs.input) downArrowSimulator(this.$refs.input)
} }
if (typeof (google.maps.places.Autocomplete) !== 'function') { if (typeof google.maps.places.Autocomplete !== 'function') {
throw new Error('google.maps.places.Autocomplete is undefined. Did you add \'places\' to libraries when loading Google Maps?') throw new Error(
"google.maps.places.Autocomplete is undefined. Did you add 'places' to libraries when loading Google Maps?"
)
} }
/* eslint-disable no-unused-vars */ /* eslint-disable no-unused-vars */
const finalOptions = { const finalOptions = {
...getPropsValues(this, mappedProps), ...getPropsValues(this, mappedProps),
...this.options ...this.options,
} }
this.$autocomplete = new google.maps.places.Autocomplete(this.$refs.input, finalOptions) this.$autocomplete = new google.maps.places.Autocomplete(this.$refs.input, finalOptions)
bindProps(this, this.$autocomplete, mappedProps) bindProps(this, this.$autocomplete, mappedProps)
this.$watch('componentRestrictions', v => { this.$watch('componentRestrictions', (v) => {
if (v !== undefined) { if (v !== undefined) {
this.$autocomplete.setComponentRestrictions(v) this.$autocomplete.setComponentRestrictions(v)
} }
@@ -66,6 +68,6 @@ export default {
}, },
props: { props: {
...mappedPropsToVueProps(mappedProps), ...mappedPropsToVueProps(mappedProps),
...props ...props,
} },
} }

View File

@@ -4,11 +4,11 @@ const props = {
center: { center: {
type: Object, type: Object,
twoWay: true, twoWay: true,
required: true required: true,
}, },
radius: { radius: {
type: Number, type: Number,
twoWay: true twoWay: true,
}, },
draggable: { draggable: {
type: Boolean, type: Boolean,
@@ -20,8 +20,8 @@ const props = {
}, },
options: { options: {
type: Object, type: Object,
twoWay: false twoWay: false,
} },
} }
const events = [ const events = [
@@ -35,7 +35,7 @@ const events = [
'mouseout', 'mouseout',
'mouseover', 'mouseover',
'mouseup', 'mouseup',
'rightclick' 'rightclick',
] ]
export default mapElementFactory({ export default mapElementFactory({

View File

@@ -7,52 +7,52 @@ import mapElementFactory from './mapElementFactory.js'
const props = { const props = {
maxZoom: { maxZoom: {
type: Number, type: Number,
twoWay: false twoWay: false,
}, },
batchSizeIE: { batchSizeIE: {
type: Number, type: Number,
twoWay: false twoWay: false,
}, },
calculator: { calculator: {
type: Function, type: Function,
twoWay: false twoWay: false,
}, },
enableRetinaIcons: { enableRetinaIcons: {
type: Boolean, type: Boolean,
twoWay: false twoWay: false,
}, },
gridSize: { gridSize: {
type: Number, type: Number,
twoWay: false twoWay: false,
}, },
ignoreHidden: { ignoreHidden: {
type: Boolean, type: Boolean,
twoWay: false twoWay: false,
}, },
imageExtension: { imageExtension: {
type: String, type: String,
twoWay: false twoWay: false,
}, },
imagePath: { imagePath: {
type: String, type: String,
twoWay: false twoWay: false,
}, },
imageSizes: { imageSizes: {
type: Array, type: Array,
twoWay: false twoWay: false,
}, },
minimumClusterSize: { minimumClusterSize: {
type: Number, type: Number,
twoWay: false twoWay: false,
}, },
styles: { styles: {
type: Array, type: Array,
twoWay: false twoWay: false,
}, },
zoomOnClick: { zoomOnClick: {
type: Boolean, type: Boolean,
twoWay: false twoWay: false,
} },
} }
const events = [ const events = [
@@ -65,7 +65,7 @@ const events = [
'mouseup', 'mouseup',
'mousedown', 'mousedown',
'mouseover', 'mouseover',
'mouseout' 'mouseout',
] ]
export default mapElementFactory({ export default mapElementFactory({
@@ -75,13 +75,17 @@ export default mapElementFactory({
ctr: () => { ctr: () => {
if (typeof MarkerClusterer === 'undefined') { if (typeof MarkerClusterer === 'undefined') {
/* eslint-disable no-console */ /* eslint-disable no-console */
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') console.error(
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') 'MarkerClusterer is not installed! require() it or include it from https://cdnjs.cloudflare.com/ajax/libs/js-marker-clusterer/1.0.0/markerclusterer.js'
)
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'
)
} }
return MarkerClusterer return MarkerClusterer
}, },
ctrArgs: ({map, ...otherOptions}) => [map, [], otherOptions], ctrArgs: ({ map, ...otherOptions }) => [map, [], otherOptions],
afterCreate (inst) { afterCreate(inst) {
const reinsertMarkers = () => { const reinsertMarkers = () => {
const oldMarkers = inst.getMarkers() const oldMarkers = inst.getMarkers()
inst.clearMarkers() inst.clearMarkers()
@@ -93,14 +97,14 @@ export default mapElementFactory({
} }
} }
}, },
updated () { updated() {
if (this.$clusterObject) { if (this.$clusterObject) {
this.$clusterObject.repaint() this.$clusterObject.repaint()
} }
}, },
beforeUnmount () { beforeUnmount() {
/* Performance optimization when destroying a large number of markers */ /* Performance optimization when destroying a large number of markers */
this.$children.forEach(marker => { this.$children.forEach((marker) => {
if (marker.$clusterObject === this.$clusterObject) { if (marker.$clusterObject === this.$clusterObject) {
marker.$clusterObject = null marker.$clusterObject = null
} }

View File

@@ -2,13 +2,90 @@
<template> <template>
<div> <div>
<div ref="flyaway"> <!-- so named because it will fly away to another component --> <div ref="flyaway">
<slot> <!-- so named because it will fly away to another component -->
</slot> <slot> </slot>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
export default (function (x) { return x.default || x })(require('./infoWindowImpl.js')) import mapElementFactory from './mapElementFactory.js'
const props = {
options: {
type: Object,
required: false,
default() {
return {}
},
},
position: {
type: Object,
twoWay: true,
},
zIndex: {
type: Number,
twoWay: true,
},
}
const events = ['domready', 'closeclick', 'content_changed']
export default mapElementFactory({
mappedProps: props,
events,
name: 'infoWindow',
ctr: () => google.maps.InfoWindow,
props: {
opened: {
type: Boolean,
default: true,
},
},
inject: {
$markerPromise: {
default: null,
},
},
mounted() {
const el = this.$refs.flyaway
el.parentNode.removeChild(el)
},
beforeCreate(options) {
options.content = this.$refs.flyaway
if (this.$markerPromise) {
delete options.position
return this.$markerPromise.then((mo) => {
this.$markerObject = mo
return mo
})
}
},
methods: {
_openInfoWindow() {
if (this.opened) {
if (this.$markerObject !== null) {
this.$infoWindowObject.open(this.$map, this.$markerObject)
} else {
this.$infoWindowObject.open(this.$map)
}
} else {
this.$infoWindowObject.close()
}
},
},
afterCreate() {
this._openInfoWindow()
this.$watch('opened', () => {
this._openInfoWindow()
})
},
})
</script> </script>

View File

@@ -1,82 +0,0 @@
import mapElementFactory from './mapElementFactory.js'
const props = {
options: {
type: Object,
required: false,
default () {
return {}
}
},
position: {
type: Object,
twoWay: true,
},
zIndex: {
type: Number,
twoWay: true,
}
}
const events = [
'domready',
'closeclick',
'content_changed',
]
export default mapElementFactory({
mappedProps: props,
events,
name: 'infoWindow',
ctr: () => google.maps.InfoWindow,
props: {
opened: {
type: Boolean,
default: true,
},
},
inject: {
'$markerPromise': {
default: null,
}
},
mounted () {
const el = this.$refs.flyaway
el.parentNode.removeChild(el)
},
beforeCreate (options) {
options.content = this.$refs.flyaway
if (this.$markerPromise) {
delete options.position
return this.$markerPromise.then(mo => {
this.$markerObject = mo
return mo
})
}
},
methods: {
_openInfoWindow () {
if (this.opened) {
if (this.$markerObject !== null) {
this.$infoWindowObject.open(this.$map, this.$markerObject)
} else {
this.$infoWindowObject.open(this.$map)
}
} else {
this.$infoWindowObject.close()
}
},
},
afterCreate () {
this._openInfoWindow()
this.$watch('opened', () => {
this._openInfoWindow()
})
}
})

View File

@@ -9,19 +9,188 @@
</template> </template>
<script> <script>
export default (function (x) { return x.default || x })(require('./mapImpl.js')) import bindEvents from '../utils/bindEvents.js'
import { bindProps, getPropsValues } from '../utils/bindProps.js'
import mountableMixin from '../utils/mountableMixin.js'
import TwoWayBindingWrapper from '../utils/TwoWayBindingWrapper.js'
import WatchPrimitiveProperties from '../utils/WatchPrimitiveProperties.js'
import { mappedPropsToVueProps } from './mapElementFactory.js'
const props = {
center: {
required: true,
twoWay: true,
type: Object,
noBind: true,
},
zoom: {
required: false,
twoWay: true,
type: Number,
noBind: true,
},
heading: {
type: Number,
twoWay: true,
},
mapTypeId: {
twoWay: true,
type: String,
},
tilt: {
twoWay: true,
type: Number,
},
options: {
type: Object,
default() {
return {}
},
},
}
const events = [
'bounds_changed',
'click',
'dblclick',
'drag',
'dragend',
'dragstart',
'idle',
'mousemove',
'mouseout',
'mouseover',
'resize',
'rightclick',
'tilesloaded',
]
// Plain Google Maps methods exposed here for convenience
const linkedMethods = ['panBy', 'panTo', 'panToBounds', 'fitBounds'].reduce((all, methodName) => {
all[methodName] = function () {
if (this.$mapObject) {
this.$mapObject[methodName].apply(this.$mapObject, arguments)
}
}
return all
}, {})
// Other convenience methods exposed by Vue Google Maps
const customMethods = {
resize() {
if (this.$mapObject) {
google.maps.event.trigger(this.$mapObject, 'resize')
}
},
resizePreserveCenter() {
if (!this.$mapObject) {
return
}
const oldCenter = this.$mapObject.getCenter()
google.maps.event.trigger(this.$mapObject, 'resize')
this.$mapObject.setCenter(oldCenter)
},
/// Override mountableMixin::_resizeCallback
/// because resizePreserveCenter is usually the
/// expected behaviour
_resizeCallback() {
this.resizePreserveCenter()
},
}
export default {
mixins: [mountableMixin],
props: mappedPropsToVueProps(props),
provide() {
this.$mapPromise = new Promise((resolve, reject) => {
this.$mapPromiseDeferred = { resolve, reject }
})
return {
$mapPromise: this.$mapPromise,
}
},
emits: ['center_changed', 'zoom_changed', 'bounds_changed'],
computed: {
finalLat() {
return this.center && typeof this.center.lat === 'function'
? this.center.lat()
: this.center.lat
},
finalLng() {
return this.center && typeof this.center.lng === 'function'
? this.center.lng()
: this.center.lng
},
finalLatLng() {
return { lat: this.finalLat, lng: this.finalLng }
},
},
watch: {
zoom(zoom) {
if (this.$mapObject) {
this.$mapObject.setZoom(zoom)
}
},
},
mounted() {
return this.$gmapApiPromiseLazy()
.then(() => {
// getting the DOM element where to create the map
const element = this.$refs['vue-map']
// creating the map
const options = {
...this.options,
...getPropsValues(this, props),
}
delete options.options
this.$mapObject = new google.maps.Map(element, options)
// binding properties (two and one way)
bindProps(this, this.$mapObject, props)
// binding events
bindEvents(this, this.$mapObject, events)
// manually trigger center and zoom
TwoWayBindingWrapper((increment, decrement, shouldUpdate) => {
this.$mapObject.addListener('center_changed', () => {
if (shouldUpdate()) {
this.$emit('center_changed', this.$mapObject.getCenter())
}
decrement()
})
const updateCenter = () => {
increment()
this.$mapObject.setCenter(this.finalLatLng)
}
WatchPrimitiveProperties(this, ['finalLat', 'finalLng'], updateCenter)
})
this.$mapObject.addListener('zoom_changed', () => {
this.$emit('zoom_changed', this.$mapObject.getZoom())
})
this.$mapObject.addListener('bounds_changed', () => {
this.$emit('bounds_changed', this.$mapObject.getBounds())
})
this.$mapPromiseDeferred.resolve(this.$mapObject)
return this.$mapObject
})
.catch((error) => {
throw error
})
},
methods: {
...customMethods,
...linkedMethods,
},
}
</script> </script>
<style lang="css">
.vue-map-container {
position: relative;
}
.vue-map-container .vue-map {
left: 0; right: 0; top: 0; bottom: 0;
position: absolute;
}
.vue-map-hidden {
display: none;
}
</style>

View File

@@ -1,149 +1,153 @@
import bindEvents from '../utils/bindEvents.js' import bindEvents from '../utils/bindEvents.js'
import {bindProps, getPropsValues} from '../utils/bindProps.js' import { bindProps, getPropsValues } from '../utils/bindProps.js'
import MapElementMixin from './mapElementMixin' import MapElementMixin from './mapElementMixin'
/** /**
* *
* @param {Object} options * @param {Object} options
* @param {Object} options.mappedProps - Definitions of props * @param {Object} options.mappedProps - Definitions of props
* @param {Object} options.mappedProps.PROP.type - Value type * @param {Object} options.mappedProps.PROP.type - Value type
* @param {Boolean} options.mappedProps.PROP.twoWay * @param {Boolean} options.mappedProps.PROP.twoWay
* - Whether the prop has a corresponding PROP_changed * - Whether the prop has a corresponding PROP_changed
* event * event
* @param {Boolean} options.mappedProps.PROP.noBind * @param {Boolean} options.mappedProps.PROP.noBind
* - If true, do not apply the default bindProps / bindEvents. * - If true, do not apply the default bindProps / bindEvents.
* However it will still be added to the list of component props * However it will still be added to the list of component props
* @param {Object} options.props - Regular Vue-style props. * @param {Object} options.props - Regular Vue-style props.
* Note: must be in the Object form because it will be * Note: must be in the Object form because it will be
* merged with the `mappedProps` * merged with the `mappedProps`
* *
* @param {Object} options.events - Google Maps API events * @param {Object} options.events - Google Maps API events
* that are not bound to a corresponding prop * that are not bound to a corresponding prop
* @param {String} options.name - e.g. `polyline` * @param {String} options.name - e.g. `polyline`
* @param {=> String} options.ctr - constructor, e.g. * @param {=> String} options.ctr - constructor, e.g.
* `google.maps.Polyline`. However, since this is not * `google.maps.Polyline`. However, since this is not
* generally available during library load, this becomes * generally available during library load, this becomes
* a function instead, e.g. () => google.maps.Polyline * a function instead, e.g. () => google.maps.Polyline
* which will be called only after the API has been loaded * which will be called only after the API has been loaded
* @param {(MappedProps, OtherVueProps) => Array} options.ctrArgs - * @param {(MappedProps, OtherVueProps) => Array} options.ctrArgs -
* If the constructor in `ctr` needs to be called with * If the constructor in `ctr` needs to be called with
* arguments other than a single `options` object, e.g. for * arguments other than a single `options` object, e.g. for
* GroundOverlay, we call `new GroundOverlay(url, bounds, options)` * GroundOverlay, we call `new GroundOverlay(url, bounds, options)`
* then pass in a function that returns the argument list as an array * then pass in a function that returns the argument list as an array
* *
* Otherwise, the constructor will be called with an `options` object, * Otherwise, the constructor will be called with an `options` object,
* with property and values merged from: * with property and values merged from:
* *
* 1. the `options` property, if any * 1. the `options` property, if any
* 2. a `map` property with the Google Maps * 2. a `map` property with the Google Maps
* 3. all the properties passed to the component in `mappedProps` * 3. all the properties passed to the component in `mappedProps`
* @param {Object => Any} options.beforeCreate - * @param {Object => Any} options.beforeCreate -
* Hook to modify the options passed to the initializer * Hook to modify the options passed to the initializer
* @param {(options.ctr, Object) => Any} options.afterCreate - * @param {(options.ctr, Object) => Any} options.afterCreate -
* Hook called when * Hook called when
* *
*/ */
export default function (options) { export default function (options) {
const { const {
mappedProps, mappedProps,
name, name,
ctr, ctr,
ctrArgs, ctrArgs,
events, events,
beforeCreate, beforeCreate,
afterCreate, afterCreate,
props, props,
...rest ...rest
} = options } = options
const promiseName = `$${name}Promise` const promiseName = `$${name}Promise`
const instanceName = `$${name}Object` const instanceName = `$${name}Object`
assert(!(rest.props instanceof Array), '`props` should be an object, not Array') assert(!(rest.props instanceof Array), '`props` should be an object, not Array')
return { return {
...(typeof GENERATE_DOC !== 'undefined' ? {$vgmOptions: options} : {}), ...(typeof GENERATE_DOC !== 'undefined' ? { $vgmOptions: options } : {}),
mixins: [MapElementMixin], mixins: [MapElementMixin],
props: { props: {
...props, ...props,
...mappedPropsToVueProps(mappedProps), ...mappedPropsToVueProps(mappedProps),
}, },
render () { return '' }, render() {
provide () { return ''
const promise = this.$mapPromise.then((map) => { },
// Infowindow needs this to be immediately available provide() {
this.$map = map const promise = this.$mapPromise
.then((map) => {
// Initialize the maps with the given options // Infowindow needs this to be immediately available
const options = { this.$map = map
...this.options,
map, // Initialize the maps with the given options
...getPropsValues(this, mappedProps) const options = {
} ...this.options,
delete options.options // delete the extra options map,
...getPropsValues(this, mappedProps),
if (beforeCreate) { }
const result = beforeCreate.bind(this)(options) delete options.options // delete the extra options
if (result instanceof Promise) { if (beforeCreate) {
return result.then(() => ({options})) const result = beforeCreate.bind(this)(options)
}
} if (result instanceof Promise) {
return {options} return result.then(() => ({ options }))
}).then(({options}) => { }
const ConstructorObject = ctr() }
// https://stackoverflow.com/questions/1606797/use-of-apply-with-new-operator-is-this-possible return { options }
this[instanceName] = ctrArgs })
? new (Function.prototype.bind.call( .then(({ options }) => {
ConstructorObject, const ConstructorObject = ctr()
null, // https://stackoverflow.com/questions/1606797/use-of-apply-with-new-operator-is-this-possible
...ctrArgs(options, getPropsValues(this, props || {})) this[instanceName] = ctrArgs
))() ? new (Function.prototype.bind.call(
: new ConstructorObject(options) ConstructorObject,
null,
bindProps(this, this[instanceName], mappedProps) ...ctrArgs(options, getPropsValues(this, props || {}))
bindEvents(this, this[instanceName], events) ))()
: new ConstructorObject(options)
if (afterCreate) {
afterCreate.bind(this)(this[instanceName]) bindProps(this, this[instanceName], mappedProps)
} bindEvents(this, this[instanceName], events)
return this[instanceName]
}) if (afterCreate) {
this[promiseName] = promise afterCreate.bind(this)(this[instanceName])
return {[promiseName]: promise} }
}, return this[instanceName]
unmounted () { })
// Note: not all Google Maps components support maps this[promiseName] = promise
if (this[instanceName] && this[instanceName].setMap) { return { [promiseName]: promise }
this[instanceName].setMap(null) },
} unmounted() {
}, // Note: not all Google Maps components support maps
...rest if (this[instanceName] && this[instanceName].setMap) {
} this[instanceName].setMap(null)
} }
},
function assert (v, message) { ...rest,
if (!v) throw new Error(message) }
} }
/** function assert(v, message) {
* Strips out the extraneous properties we have in our if (!v) throw new Error(message)
* props definitions }
* @param {Object} props
*/ /**
export function mappedPropsToVueProps (mappedProps) { * Strips out the extraneous properties we have in our
return Object.entries(mappedProps) * props definitions
.map(([key, prop]) => { * @param {Object} props
const value = {} */
export function mappedPropsToVueProps(mappedProps) {
if ('type' in prop) value.type = prop.type return Object.entries(mappedProps)
if ('default' in prop) value.default = prop.default .map(([key, prop]) => {
if ('required' in prop) value.required = prop.required const value = {}
return [key, value] if ('type' in prop) value.type = prop.type
}) if ('default' in prop) value.default = prop.default
.reduce((acc, [key, val]) => { if ('required' in prop) value.required = prop.required
acc[key] = val
return acc return [key, value]
}, {}) })
} .reduce((acc, [key, val]) => {
acc[key] = val
return acc
}, {})
}

View File

@@ -9,10 +9,10 @@
* */ * */
export default { export default {
inject: { inject: {
'$mapPromise': { default: 'abcdef' } $mapPromise: { default: 'abcdef' },
}, },
provide () { provide() {
// Note: although this mixin is not "providing" anything, // Note: although this mixin is not "providing" anything,
// components' expect the `$map` property to be present on the component. // components' expect the `$map` property to be present on the component.
// In order for that to happen, this mixin must intercept the $mapPromise // In order for that to happen, this mixin must intercept the $mapPromise

View File

@@ -1,5 +1,5 @@
import bindEvents from '../utils/bindEvents.js' import bindEvents from '../utils/bindEvents.js'
import {bindProps, getPropsValues} from '../utils/bindProps.js' import { bindProps, getPropsValues } from '../utils/bindProps.js'
import mountableMixin from '../utils/mountableMixin.js' import mountableMixin from '../utils/mountableMixin.js'
import TwoWayBindingWrapper from '../utils/TwoWayBindingWrapper.js' import TwoWayBindingWrapper from '../utils/TwoWayBindingWrapper.js'
@@ -25,7 +25,7 @@ const props = {
}, },
mapTypeId: { mapTypeId: {
twoWay: true, twoWay: true,
type: String type: String,
}, },
tilt: { tilt: {
twoWay: true, twoWay: true,
@@ -33,8 +33,10 @@ const props = {
}, },
options: { options: {
type: Object, type: Object,
default () { return {} } default() {
} return {}
},
},
} }
const events = [ const events = [
@@ -54,27 +56,26 @@ const events = [
] ]
// Plain Google Maps methods exposed here for convenience // Plain Google Maps methods exposed here for convenience
const linkedMethods = [ const linkedMethods = ['panBy', 'panTo', 'panToBounds', 'fitBounds'].reduce((all, methodName) => {
'panBy',
'panTo',
'panToBounds',
'fitBounds'
].reduce((all, methodName) => {
all[methodName] = function () { all[methodName] = function () {
if (this.$mapObject) { this.$mapObject[methodName].apply(this.$mapObject, arguments) } if (this.$mapObject) {
this.$mapObject[methodName].apply(this.$mapObject, arguments)
}
} }
return all return all
}, {}) }, {})
// Other convenience methods exposed by Vue Google Maps // Other convenience methods exposed by Vue Google Maps
const customMethods = { const customMethods = {
resize () { resize() {
if (this.$mapObject) { if (this.$mapObject) {
google.maps.event.trigger(this.$mapObject, 'resize') google.maps.event.trigger(this.$mapObject, 'resize')
} }
}, },
resizePreserveCenter () { resizePreserveCenter() {
if (!this.$mapObject) { return } if (!this.$mapObject) {
return
}
const oldCenter = this.$mapObject.getCenter() const oldCenter = this.$mapObject.getCenter()
google.maps.event.trigger(this.$mapObject, 'resize') google.maps.event.trigger(this.$mapObject, 'resize')
@@ -84,98 +85,97 @@ const customMethods = {
/// Override mountableMixin::_resizeCallback /// Override mountableMixin::_resizeCallback
/// because resizePreserveCenter is usually the /// because resizePreserveCenter is usually the
/// expected behaviour /// expected behaviour
_resizeCallback () { _resizeCallback() {
this.resizePreserveCenter() this.resizePreserveCenter()
} },
} }
export default { export default {
mixins: [mountableMixin], mixins: [mountableMixin],
props: mappedPropsToVueProps(props), props: mappedPropsToVueProps(props),
provide () { provide() {
this.$mapPromise = new Promise((resolve, reject) => { this.$mapPromise = new Promise((resolve, reject) => {
this.$mapPromiseDeferred = { resolve, reject } this.$mapPromiseDeferred = { resolve, reject }
}) })
return { return {
'$mapPromise': this.$mapPromise $mapPromise: this.$mapPromise,
} }
}, },
emits: ['center_changed', 'zoom_changed', 'bounds_changed'], emits: ['center_changed', 'zoom_changed', 'bounds_changed'],
computed: { computed: {
finalLat () { finalLat() {
return this.center && return this.center && typeof this.center.lat === 'function'
(typeof this.center.lat === 'function') ? this.center.lat() : this.center.lat ? this.center.lat()
: this.center.lat
}, },
finalLng () { finalLng() {
return this.center && return this.center && typeof this.center.lng === 'function'
(typeof this.center.lng === 'function') ? this.center.lng() : this.center.lng ? this.center.lng()
: this.center.lng
},
finalLatLng() {
return { lat: this.finalLat, lng: this.finalLng }
}, },
finalLatLng () {
return {lat: this.finalLat, lng: this.finalLng}
}
}, },
watch: { watch: {
zoom (zoom) { zoom(zoom) {
if (this.$mapObject) { if (this.$mapObject) {
this.$mapObject.setZoom(zoom) this.$mapObject.setZoom(zoom)
} }
} },
}, },
mounted () { mounted() {
return this.$gmapApiPromiseLazy().then(() => { return this.$gmapApiPromiseLazy()
// getting the DOM element where to create the map .then(() => {
const element = this.$refs['vue-map'] // getting the DOM element where to create the map
const element = this.$refs['vue-map']
// creating the map // creating the map
const options = { const options = {
...this.options, ...this.options,
...getPropsValues(this, props), ...getPropsValues(this, props),
} }
delete options.options delete options.options
this.$mapObject = new google.maps.Map(element, options) this.$mapObject = new google.maps.Map(element, options)
// binding properties (two and one way) // binding properties (two and one way)
bindProps(this, this.$mapObject, props) bindProps(this, this.$mapObject, props)
// binding events // binding events
bindEvents(this, this.$mapObject, events) bindEvents(this, this.$mapObject, events)
// manually trigger center and zoom // manually trigger center and zoom
TwoWayBindingWrapper((increment, decrement, shouldUpdate) => { TwoWayBindingWrapper((increment, decrement, shouldUpdate) => {
this.$mapObject.addListener('center_changed', () => { this.$mapObject.addListener('center_changed', () => {
if (shouldUpdate()) { if (shouldUpdate()) {
this.$emit('center_changed', this.$mapObject.getCenter()) this.$emit('center_changed', this.$mapObject.getCenter())
}
decrement()
})
const updateCenter = () => {
increment()
this.$mapObject.setCenter(this.finalLatLng)
} }
decrement()
WatchPrimitiveProperties(this, ['finalLat', 'finalLng'], updateCenter)
})
this.$mapObject.addListener('zoom_changed', () => {
this.$emit('zoom_changed', this.$mapObject.getZoom())
})
this.$mapObject.addListener('bounds_changed', () => {
this.$emit('bounds_changed', this.$mapObject.getBounds())
}) })
const updateCenter = () => { this.$mapPromiseDeferred.resolve(this.$mapObject)
increment()
this.$mapObject.setCenter(this.finalLatLng)
}
WatchPrimitiveProperties( return this.$mapObject
this,
['finalLat', 'finalLng'],
updateCenter
)
}) })
this.$mapObject.addListener('zoom_changed', () => { .catch((error) => {
this.$emit('zoom_changed', this.$mapObject.getZoom()) throw error
}) })
this.$mapObject.addListener('bounds_changed', () => {
this.$emit('bounds_changed', this.$mapObject.getBounds())
})
this.$mapPromiseDeferred.resolve(this.$mapObject)
return this.$mapObject
})
.catch((error) => {
throw error
})
}, },
methods: { methods: {
...customMethods, ...customMethods,

View File

@@ -3,7 +3,7 @@ import mapElementFactory from './mapElementFactory.js'
const props = { const props = {
animation: { animation: {
twoWay: true, twoWay: true,
type: Number type: Number,
}, },
attribution: { attribution: {
type: Object, type: Object,
@@ -11,31 +11,30 @@ const props = {
clickable: { clickable: {
type: Boolean, type: Boolean,
twoWay: true, twoWay: true,
default: true default: true,
}, },
cursor: { cursor: {
type: String, type: String,
twoWay: true twoWay: true,
}, },
draggable: { draggable: {
type: Boolean, type: Boolean,
twoWay: true, twoWay: true,
default: false default: false,
}, },
icon: { icon: {
twoWay: true twoWay: true,
},
label: {
}, },
label: {},
opacity: { opacity: {
type: Number, type: Number,
default: 1 default: 1,
}, },
options: { options: {
type: Object type: Object,
}, },
place: { place: {
type: Object type: Object,
}, },
position: { position: {
type: Object, type: Object,
@@ -43,15 +42,15 @@ const props = {
}, },
shape: { shape: {
type: Object, type: Object,
twoWay: true twoWay: true,
}, },
title: { title: {
type: String, type: String,
twoWay: true twoWay: true,
}, },
zIndex: { zIndex: {
type: Number, type: Number,
twoWay: true twoWay: true,
}, },
visible: { visible: {
twoWay: true, twoWay: true,
@@ -69,7 +68,7 @@ const events = [
'mouseup', 'mouseup',
'mousedown', 'mousedown',
'mouseover', 'mouseover',
'mouseout' 'mouseout',
] ]
/** /**
@@ -91,26 +90,26 @@ export default mapElementFactory({
ctr: () => google.maps.Marker, ctr: () => google.maps.Marker,
inject: { inject: {
'$clusterPromise': { $clusterPromise: {
default: null, default: null,
}, },
}, },
render (h) { render(h) {
if (!this.$slots.default || this.$slots.default.length === 0) { if (!this.$slots.default || this.$slots.default.length === 0) {
return '' return ''
} else if (this.$slots.default.length === 1) { // So that infowindows can have a marker parent } else if (this.$slots.default.length === 1) {
// So that infowindows can have a marker parent
return this.$slots.default[0] return this.$slots.default[0]
} else { } else {
return h( return h('div', this.$slots.default)
'div',
this.$slots.default
)
} }
}, },
emits: ['center_changed', 'zoom_changed', 'bounds_changed'], emits: ['center_changed', 'zoom_changed', 'bounds_changed'],
unmounted () { unmounted() {
if (!this.$markerObject) { return } if (!this.$markerObject) {
return
}
if (this.$clusterObject) { if (this.$clusterObject) {
// Repaint will be performed in `updated()` of cluster // Repaint will be performed in `updated()` of cluster
@@ -120,7 +119,7 @@ export default mapElementFactory({
} }
}, },
beforeCreate (options) { beforeCreate(options) {
if (this.$clusterPromise) { if (this.$clusterPromise) {
options.map = null options.map = null
} }
@@ -128,7 +127,7 @@ export default mapElementFactory({
return this.$clusterPromise return this.$clusterPromise
}, },
afterCreate (inst) { afterCreate(inst) {
if (this.$clusterPromise) { if (this.$clusterPromise) {
this.$clusterPromise.then((co) => { this.$clusterPromise.then((co) => {
co.addMarker(inst) co.addMarker(inst)

View File

@@ -1,10 +0,0 @@
<template>
<label>
<span v-text="label"></span>
<input type="text" :placeholder="placeholder" :class="className"
ref="input"/>
</label>
</template>
<script src="./placeInputImpl.js">
</script>

View File

@@ -1,75 +0,0 @@
import {bindProps, getPropsValues} from '../utils/bindProps.js'
import downArrowSimulator from '../utils/simulateArrowDown.js'
const props = {
bounds: {
type: Object,
},
defaultPlace: {
type: String,
default: '',
},
componentRestrictions: {
type: Object,
default: null,
},
types: {
type: Array,
default: function () {
return []
}
},
placeholder: {
required: false,
type: String
},
className: {
required: false,
type: String
},
label: {
required: false,
type: String,
default: null
},
selectFirstOnEnter: {
require: false,
type: Boolean,
default: false
}
}
export default {
mounted () {
const input = this.$refs.input
// Allow default place to be set
input.value = this.defaultPlace
this.$watch('defaultPlace', () => {
input.value = this.defaultPlace
})
this.$gmapApiPromiseLazy().then(() => {
const options = getPropsValues(this, props)
if (this.selectFirstOnEnter) {
downArrowSimulator(this.$refs.input)
}
if (typeof (google.maps.places.Autocomplete) !== 'function') {
throw new Error('google.maps.places.Autocomplete is undefined. Did you add \'places\' to libraries when loading Google Maps?')
}
this.autoCompleter = new google.maps.places.Autocomplete(this.$refs.input, options)
const {placeholder, place, defaultPlace, className, label, selectFirstOnEnter, ...rest} = props // eslint-disable-line
bindProps(this, this.autoCompleter, rest)
this.autoCompleter.addListener('place_changed', () => {
this.$emit('place_changed', this.autoCompleter.getPlace())
})
})
},
created () {
console.warn('The PlaceInput class is deprecated! Please consider using the Autocomplete input instead') // eslint-disable-line no-console
},
props: props,
}

View File

@@ -2,13 +2,13 @@ import mapElementFactory from './mapElementFactory.js'
const props = { const props = {
draggable: { draggable: {
type: Boolean type: Boolean,
}, },
editable: { editable: {
type: Boolean, type: Boolean,
}, },
options: { options: {
type: Object type: Object,
}, },
path: { path: {
type: Array, type: Array,
@@ -33,88 +33,98 @@ const events = [
'mouseout', 'mouseout',
'mouseover', 'mouseover',
'mouseup', 'mouseup',
'rightclick' 'rightclick',
] ]
export default mapElementFactory({ export default mapElementFactory({
props: { props: {
deepWatch: { deepWatch: {
type: Boolean, type: Boolean,
default: false default: false,
} },
}, },
events, events,
mappedProps: props, mappedProps: props,
name: 'polygon', name: 'polygon',
ctr: () => google.maps.Polygon, ctr: () => google.maps.Polygon,
beforeCreate (options) { beforeCreate(options) {
if (!options.path) delete options.path if (!options.path) delete options.path
if (!options.paths) delete options.paths if (!options.paths) delete options.paths
}, },
afterCreate (inst) { afterCreate(inst) {
var clearEvents = () => {} var clearEvents = () => {}
// Watch paths, on our own, because we do not want to set either when it is // Watch paths, on our own, because we do not want to set either when it is
// empty // empty
this.$watch('paths', (paths) => { this.$watch(
if (paths) { 'paths',
clearEvents() (paths) => {
if (paths) {
clearEvents()
inst.setPaths(paths) inst.setPaths(paths)
const updatePaths = () => { const updatePaths = () => {
this.$emit('paths_changed', inst.getPaths()) this.$emit('paths_changed', inst.getPaths())
}
const eventListeners = []
const mvcArray = inst.getPaths()
for (let i = 0; i < mvcArray.getLength(); i++) {
let mvcPath = mvcArray.getAt(i)
eventListeners.push([mvcPath, mvcPath.addListener('insert_at', updatePaths)])
eventListeners.push([mvcPath, mvcPath.addListener('remove_at', updatePaths)])
eventListeners.push([mvcPath, mvcPath.addListener('set_at', updatePaths)])
}
eventListeners.push([mvcArray, mvcArray.addListener('insert_at', updatePaths)])
eventListeners.push([mvcArray, mvcArray.addListener('remove_at', updatePaths)])
eventListeners.push([mvcArray, mvcArray.addListener('set_at', updatePaths)])
clearEvents = () => {
eventListeners.map((
[obj, listenerHandle] // eslint-disable-line no-unused-vars
) => google.maps.event.removeListener(listenerHandle))
}
} }
const eventListeners = [] },
{
deep: this.deepWatch,
immediate: true,
}
)
this.$watch(
'path',
(path) => {
if (path) {
clearEvents()
inst.setPaths(path)
const mvcPath = inst.getPath()
const eventListeners = []
const updatePaths = () => {
this.$emit('path_changed', inst.getPath())
}
const mvcArray = inst.getPaths()
for (let i = 0; i < mvcArray.getLength(); i++) {
let mvcPath = mvcArray.getAt(i)
eventListeners.push([mvcPath, mvcPath.addListener('insert_at', updatePaths)]) eventListeners.push([mvcPath, mvcPath.addListener('insert_at', updatePaths)])
eventListeners.push([mvcPath, mvcPath.addListener('remove_at', updatePaths)]) eventListeners.push([mvcPath, mvcPath.addListener('remove_at', updatePaths)])
eventListeners.push([mvcPath, mvcPath.addListener('set_at', updatePaths)]) eventListeners.push([mvcPath, mvcPath.addListener('set_at', updatePaths)])
}
eventListeners.push([mvcArray, mvcArray.addListener('insert_at', updatePaths)])
eventListeners.push([mvcArray, mvcArray.addListener('remove_at', updatePaths)])
eventListeners.push([mvcArray, mvcArray.addListener('set_at', updatePaths)])
clearEvents = () => { clearEvents = () => {
eventListeners.map(([obj, listenerHandle]) => // eslint-disable-line no-unused-vars eventListeners.map((
google.maps.event.removeListener(listenerHandle)) [obj, listenerHandle] // eslint-disable-line no-unused-vars
) => google.maps.event.removeListener(listenerHandle))
}
} }
},
{
deep: this.deepWatch,
immediate: true,
} }
}, { )
deep: this.deepWatch, },
immediate: true,
})
this.$watch('path', (path) => {
if (path) {
clearEvents()
inst.setPaths(path)
const mvcPath = inst.getPath()
const eventListeners = []
const updatePaths = () => {
this.$emit('path_changed', inst.getPath())
}
eventListeners.push([mvcPath, mvcPath.addListener('insert_at', updatePaths)])
eventListeners.push([mvcPath, mvcPath.addListener('remove_at', updatePaths)])
eventListeners.push([mvcPath, mvcPath.addListener('set_at', updatePaths)])
clearEvents = () => {
eventListeners.map(([obj, listenerHandle]) => // eslint-disable-line no-unused-vars
google.maps.event.removeListener(listenerHandle))
}
}
}, {
deep: this.deepWatch,
immediate: true,
})
}
}) })

View File

@@ -2,18 +2,18 @@ import mapElementFactory from './mapElementFactory.js'
const props = { const props = {
draggable: { draggable: {
type: Boolean type: Boolean,
}, },
editable: { editable: {
type: Boolean, type: Boolean,
}, },
options: { options: {
twoWay: false, twoWay: false,
type: Object type: Object,
}, },
path: { path: {
type: Array, type: Array,
twoWay: true twoWay: true,
}, },
} }
@@ -28,7 +28,7 @@ const events = [
'mouseout', 'mouseout',
'mouseover', 'mouseover',
'mouseup', 'mouseup',
'rightclick' 'rightclick',
] ]
export default mapElementFactory({ export default mapElementFactory({
@@ -37,41 +37,46 @@ export default mapElementFactory({
deepWatch: { deepWatch: {
type: Boolean, type: Boolean,
default: false, default: false,
} },
}, },
events, events,
name: 'polyline', name: 'polyline',
ctr: () => google.maps.Polyline, ctr: () => google.maps.Polyline,
afterCreate (inst) { afterCreate() {
var clearEvents = () => {} var clearEvents = () => {}
this.$watch('path', (path) => { this.$watch(
if (path) { 'path',
clearEvents() (path) => {
if (path) {
clearEvents()
this.$polylineObject.setPath(path) this.$polylineObject.setPath(path)
const mvcPath = this.$polylineObject.getPath() const mvcPath = this.$polylineObject.getPath()
const eventListeners = [] const eventListeners = []
const updatePaths = () => { const updatePaths = () => {
this.$emit('path_changed', this.$polylineObject.getPath()) this.$emit('path_changed', this.$polylineObject.getPath())
} }
eventListeners.push([mvcPath, mvcPath.addListener('insert_at', updatePaths)]) eventListeners.push([mvcPath, mvcPath.addListener('insert_at', updatePaths)])
eventListeners.push([mvcPath, mvcPath.addListener('remove_at', updatePaths)]) eventListeners.push([mvcPath, mvcPath.addListener('remove_at', updatePaths)])
eventListeners.push([mvcPath, mvcPath.addListener('set_at', updatePaths)]) eventListeners.push([mvcPath, mvcPath.addListener('set_at', updatePaths)])
clearEvents = () => { clearEvents = () => {
eventListeners.map(([obj, listenerHandle]) => // eslint-disable-line no-unused-vars eventListeners.map((
google.maps.event.removeListener(listenerHandle)) [obj, listenerHandle] // eslint-disable-line no-unused-vars
) => google.maps.event.removeListener(listenerHandle))
}
} }
},
{
deep: this.deepWatch,
immediate: true,
} }
}, { )
deep: this.deepWatch, },
immediate: true,
})
}
}) })

View File

@@ -3,7 +3,7 @@ import mapElementFactory from './mapElementFactory.js'
const props = { const props = {
bounds: { bounds: {
type: Object, type: Object,
twoWay: true twoWay: true,
}, },
draggable: { draggable: {
type: Boolean, type: Boolean,
@@ -15,8 +15,8 @@ const props = {
}, },
options: { options: {
type: Object, type: Object,
twoWay: false twoWay: false,
} },
} }
const events = [ const events = [
@@ -30,7 +30,7 @@ const events = [
'mouseout', 'mouseout',
'mouseover', 'mouseover',
'mouseup', 'mouseup',
'rightclick' 'rightclick',
] ]
export default mapElementFactory({ export default mapElementFactory({

View File

@@ -1,21 +0,0 @@
<template>
<div class="vue-street-view-pano-container">
<div ref="vue-street-view-pano" class="vue-street-view-pano"></div>
<slot></slot>
</div>
</template>
<script>
export default (function (x) { return x.default || x })(require('./streetViewPanoramaImpl.js'))
</script>
<style lang="css">
.vue-street-view-pano-container {
position: relative;
}
.vue-street-view-pano-container .vue-street-view-pano {
left: 0; right: 0; top: 0; bottom: 0;
position: absolute;
}
</style>

View File

@@ -1,147 +0,0 @@
import bindEvents from '../utils/bindEvents.js'
import {bindProps, getPropsValues} from '../utils/bindProps.js'
import mountableMixin from '../utils/mountableMixin.js'
import TwoWayBindingWrapper from '../utils/TwoWayBindingWrapper.js'
import WatchPrimitiveProperties from '../utils/WatchPrimitiveProperties.js'
import { mappedPropsToVueProps } from './mapElementFactory.js'
const props = {
zoom: {
twoWay: true,
type: Number
},
pov: {
twoWay: true,
type: Object,
trackProperties: ['pitch', 'heading']
},
position: {
twoWay: true,
type: Object,
noBind: true,
},
pano: {
twoWay: true,
type: String
},
motionTracking: {
twoWay: false,
type: Boolean
},
visible: {
twoWay: true,
type: Boolean,
default: true,
},
options: {
twoWay: false,
type: Object,
default () { return {} }
}
}
const events = [
'closeclick',
'status_changed',
]
export default {
mixins: [mountableMixin],
props: mappedPropsToVueProps(props),
replace: false, // necessary for css styles
methods: {
resize () {
if (this.$panoObject) {
google.maps.event.trigger(this.$panoObject, 'resize')
}
},
},
provide () {
const promise = new Promise((resolve, reject) => {
this.$panoPromiseDeferred = {resolve, reject}
})
return {
'$panoPromise': promise,
'$mapPromise': promise, // so that we can use it with markers
}
},
computed: {
finalLat () {
return this.position &&
(typeof this.position.lat === 'function') ? this.position.lat() : this.position.lat
},
finalLng () {
return this.position &&
(typeof this.position.lng === 'function') ? this.position.lng() : this.position.lng
},
finalLatLng () {
return {
lat: this.finalLat,
lng: this.finalLng,
}
}
},
watch: {
zoom (zoom) {
if (this.$panoObject) {
this.$panoObject.setZoom(zoom)
}
}
},
mounted () {
return this.$gmapApiPromiseLazy().then(() => {
// getting the DOM element where to create the map
const element = this.$refs['vue-street-view-pano']
// creating the map
const options = {
...this.options,
...getPropsValues(this, props),
}
delete options.options
this.$panoObject = new google.maps.StreetViewPanorama(element, options)
// binding properties (two and one way)
bindProps(this, this.$panoObject, props)
// binding events
bindEvents(this, this.$panoObject, events)
// manually trigger position
TwoWayBindingWrapper((increment, decrement, shouldUpdate) => {
// Panos take a while to load
increment()
this.$panoObject.addListener('position_changed', () => {
if (shouldUpdate()) {
this.$emit('position_changed', this.$panoObject.getPosition())
}
decrement()
})
const updateCenter = () => {
increment()
this.$panoObject.setPosition(this.finalLatLng)
}
WatchPrimitiveProperties(
this,
['finalLat', 'finalLng'],
updateCenter
)
})
this.$panoPromiseDeferred.resolve(this.$panoObject)
return this.$panoPromise
})
.catch((error) => {
throw error
})
},
}

View File

@@ -1,47 +1,44 @@
import lazy from './utils/lazyValue' import lazy from './utils/lazyValue'
import {loadGmapApi} from './manager' import { loadGmapApi } from './manager'
import {createApp} from 'vue' import { createApp } from 'vue'
import Marker from './components/marker' import Marker from './components/marker'
import Polyline from './components/polyline' import Polyline from './components/polyline'
import Polygon from './components/polygon' import Polygon from './components/polygon'
import Circle from './components/circle' import Circle from './components/circle'
import Rectangle from './components/rectangle' import Rectangle from './components/rectangle'
import GmapCluster from './components/cluster' import GmapCluster from './components/cluster.vue'
// Vue component imports
import InfoWindow from './components/infoWindow.vue' import InfoWindow from './components/infoWindow.vue'
import Map from './components/map.vue' import Map from './components/map.vue'
import StreetViewPanorama from './components/streetViewPanorama.vue'
import PlaceInput from './components/placeInput.vue'
import Autocomplete from './components/autocomplete.vue' import Autocomplete from './components/autocomplete.vue'
import MapElementMixin from './components/mapElementMixin' import MapElementMixin from './components/mapElementMixin'
import MapElementFactory from './components/mapElementFactory' import MapElementFactory from './components/mapElementFactory'
import MountableMixin from './utils/mountableMixin' import MountableMixin from './utils/mountableMixin'
// HACK: Cluster should be loaded conditionally
// However in the web version, it's not possible to write
// `import 'vue2-google-maps/src/components/cluster'`, so we need to
// import it anyway (but we don't have to register it)
// Therefore we use babel-plugin-transform-inline-environment-variables to
// set BUILD_DEV to truthy / falsy
const Cluster = (process.env.BUILD_DEV === '1')
? undefined
: (s => s.default || s)(require('./components/cluster'))
let GmapApi = null let GmapApi = null
// export everything // export everything
export {loadGmapApi, Marker, Polyline, Polygon, Circle, Cluster, Rectangle, export {
InfoWindow, Map, PlaceInput, MapElementMixin, MapElementFactory, Autocomplete, loadGmapApi,
MountableMixin, StreetViewPanorama} Marker,
Polyline,
Polygon,
Circle,
GmapCluster,
Rectangle,
InfoWindow,
Map,
MapElementMixin,
MapElementFactory,
Autocomplete,
MountableMixin,
}
export function install (Vue, options) { export function install(Vue, options) {
// Set defaults // Set defaults
options = { options = {
installComponents: true, installComponents: true,
autobindAllEvents: false, autobindAllEvents: false,
...options ...options,
} }
// Update the global `GmapApi`. This will allow // Update the global `GmapApi`. This will allow
@@ -49,7 +46,11 @@ export function install (Vue, options) {
// via: // via:
// import {gmapApi} from 'vue2-google-maps' // import {gmapApi} from 'vue2-google-maps'
// export default { computed: { google: gmapApi } } // export default { computed: { google: gmapApi } }
GmapApi = createApp({data: {gmapApi: null}}); GmapApi = createApp({
data: function () {
return { gmapApi: null }
},
})
const defaultResizeBus = createApp() const defaultResizeBus = createApp()
@@ -58,11 +59,11 @@ export function install (Vue, options) {
let gmapApiPromiseLazy = makeGmapApiPromiseLazy(options) let gmapApiPromiseLazy = makeGmapApiPromiseLazy(options)
Vue.mixin({ Vue.mixin({
created () { created() {
this.$gmapDefaultResizeBus = defaultResizeBus this.$gmapDefaultResizeBus = defaultResizeBus
this.$gmapOptions = options this.$gmapOptions = options
this.$gmapApiPromiseLazy = gmapApiPromiseLazy this.$gmapApiPromiseLazy = gmapApiPromiseLazy
} },
}) })
Vue.$gmapDefaultResizeBus = defaultResizeBus Vue.$gmapDefaultResizeBus = defaultResizeBus
Vue.$gmapApiPromiseLazy = gmapApiPromiseLazy Vue.$gmapApiPromiseLazy = gmapApiPromiseLazy
@@ -77,22 +78,23 @@ export function install (Vue, options) {
Vue.component('GmapCircle', Circle) Vue.component('GmapCircle', Circle)
Vue.component('GmapRectangle', Rectangle) Vue.component('GmapRectangle', Rectangle)
Vue.component('GmapAutocomplete', Autocomplete) Vue.component('GmapAutocomplete', Autocomplete)
Vue.component('GmapPlaceInput', PlaceInput)
Vue.component('GmapStreetViewPanorama', StreetViewPanorama)
} }
} }
function makeGmapApiPromiseLazy (options) { function makeGmapApiPromiseLazy(options) {
// Things to do once the API is loaded // Things to do once the API is loaded
function onApiLoaded () { function onApiLoaded() {
GmapApi.gmapApi = {} GmapApi.gmapApi = {}
return window.google return window.google
} }
if (options.load) { // If library should load the API if (options.load) {
return lazy(() => { // Load the // If library should load the API
return lazy(() => {
// Load the
// This will only be evaluated once // This will only be evaluated once
if (typeof window === 'undefined') { // server side -- never resolve this promise if (typeof window === 'undefined') {
// server side -- never resolve this promise
return new Promise(() => {}).then(onApiLoaded) return new Promise(() => {}).then(onApiLoaded)
} else { } else {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
@@ -102,11 +104,11 @@ function makeGmapApiPromiseLazy (options) {
} catch (err) { } catch (err) {
reject(err) reject(err)
} }
}) }).then(onApiLoaded)
.then(onApiLoaded)
} }
}) })
} else { // If library should not handle API, provide } else {
// If library should not handle API, provide
// end-users with the global `vueGoogleMapsInit: () => undefined` // end-users with the global `vueGoogleMapsInit: () => undefined`
// when the Google Maps API has been loaded // when the Google Maps API has been loaded
const promise = new Promise((resolve) => { const promise = new Promise((resolve) => {
@@ -121,6 +123,6 @@ function makeGmapApiPromiseLazy (options) {
} }
} }
export function gmapApi () { export function gmapApi() {
return GmapApi.gmapApi && window.google return GmapApi.gmapApi && window.google
} }

View File

@@ -47,6 +47,7 @@ export const loadGmapApi = (options, loadCn) => {
} }
// libraries // libraries
/* eslint-disable no-prototype-builtins */
if (Array.prototype.isPrototypeOf(options.libraries)) { if (Array.prototype.isPrototypeOf(options.libraries)) {
options.libraries = options.libraries.join(',') options.libraries = options.libraries.join(',')
} }
@@ -58,7 +59,9 @@ export const loadGmapApi = (options, loadCn) => {
baseUrl = 'https://maps.google.cn/' baseUrl = 'https://maps.google.cn/'
} }
let url = baseUrl + 'maps/api/js?' + let url =
baseUrl +
'maps/api/js?' +
Object.keys(options) Object.keys(options)
.map((key) => encodeURIComponent(key) + '=' + encodeURIComponent(options[key])) .map((key) => encodeURIComponent(key) + '=' + encodeURIComponent(options[key]))
.join('&') .join('&')

View File

@@ -1,48 +1,52 @@
/** /**
* When you have two-way bindings, but the actual bound value will not equal * When you have two-way bindings, but the actual bound value will not equal
* the value you initially passed in, then to avoid an infinite loop you * the value you initially passed in, then to avoid an infinite loop you
* need to increment a counter every time you pass in a value, decrement the * need to increment a counter every time you pass in a value, decrement the
* same counter every time the bound value changed, but only bubble up * same counter every time the bound value changed, but only bubble up
* the event when the counter is zero. * the event when the counter is zero.
* *
Example: Example:
Let's say DrawingRecognitionCanvas is a deep-learning backed canvas Let's say DrawingRecognitionCanvas is a deep-learning backed canvas
that, when given the name of an object (e.g. 'dog'), draws a dog. that, when given the name of an object (e.g. 'dog'), draws a dog.
But whenever the drawing on it changes, it also sends back its interpretation But whenever the drawing on it changes, it also sends back its interpretation
of the image by way of the @newObjectRecognized event. of the image by way of the @newObjectRecognized event.
<input <input
type="text" type="text"
placeholder="an object, e.g. Dog, Cat, Frog" placeholder="an object, e.g. Dog, Cat, Frog"
v-model="identifiedObject" /> v-model="identifiedObject" />
<DrawingRecognitionCanvas <DrawingRecognitionCanvas
:object="identifiedObject" :object="identifiedObject"
@newObjectRecognized="identifiedObject = $event" @newObjectRecognized="identifiedObject = $event"
/> />
new TwoWayBindingWrapper((increment, decrement, shouldUpdate) => { new TwoWayBindingWrapper((increment, decrement, shouldUpdate) => {
this.$watch('identifiedObject', () => { this.$watch('identifiedObject', () => {
// new object passed in // new object passed in
increment() increment()
}) })
this.$deepLearningBackend.on('drawingChanged', () => { this.$deepLearningBackend.on('drawingChanged', () => {
recognizeObject(this.$deepLearningBackend) recognizeObject(this.$deepLearningBackend)
.then((object) => { .then((object) => {
decrement() decrement()
if (shouldUpdate()) { if (shouldUpdate()) {
this.$emit('newObjectRecognized', object.name) this.$emit('newObjectRecognized', object.name)
} }
}) })
}) })
}) })
*/ */
export default function TwoWayBindingWrapper (fn) { export default function TwoWayBindingWrapper(fn) {
let counter = 0 let counter = 0
fn( fn(
() => { counter += 1 }, () => {
() => { counter = Math.max(0, counter - 1) }, counter += 1
() => counter === 0, },
) () => {
} counter = Math.max(0, counter - 1)
},
() => counter === 0
)
}

View File

@@ -1,24 +1,29 @@
/** /**
* Watch the individual properties of a PoD object, instead of the object * Watch the individual properties of a PoD object, instead of the object
* per se. This is different from a deep watch where both the reference * per se. This is different from a deep watch where both the reference
* and the individual values are watched. * and the individual values are watched.
* *
* In effect, it throttles the multiple $watch to execute at most once per tick. * In effect, it throttles the multiple $watch to execute at most once per tick.
*/ */
export default function WatchPrimitiveProperties (vueInst, propertiesToTrack, handler, immediate = false) { export default function WatchPrimitiveProperties(
let isHandled = false vueInst,
propertiesToTrack,
function requestHandle () { handler,
if (!isHandled) { immediate = false
isHandled = true ) {
vueInst.$nextTick(() => { let isHandled = false
isHandled = false
handler() function requestHandle() {
}) if (!isHandled) {
} isHandled = true
} vueInst.$nextTick(() => {
isHandled = false
for (let prop of propertiesToTrack) { handler()
vueInst.$watch(prop, requestHandle, {immediate}) })
} }
} }
for (let prop of propertiesToTrack) {
vueInst.$watch(prop, requestHandle, { immediate })
}
}

View File

@@ -1,7 +1,6 @@
export default (vueInst, googleMapsInst, events) => { export default (vueInst, googleMapsInst, events) => {
for (let eventName of events) { for (let eventName of events) {
if (vueInst.$gmapOptions.autobindAllEvents || if (vueInst.$gmapOptions.autobindAllEvents || vueInst.$attrs[eventName]) {
vueInst.$attrs[eventName]) {
googleMapsInst.addListener(eventName, (ev) => { googleMapsInst.addListener(eventName, (ev) => {
vueInst.$emit(eventName, ev) vueInst.$emit(eventName, ev)
}) })

View File

@@ -1,32 +1,28 @@
import WatchPrimitiveProperties from '../utils/WatchPrimitiveProperties' import WatchPrimitiveProperties from '../utils/WatchPrimitiveProperties'
function capitalizeFirstLetter (string) { function capitalizeFirstLetter(string) {
return string.charAt(0).toUpperCase() + string.slice(1) return string.charAt(0).toUpperCase() + string.slice(1)
} }
export function getPropsValues (vueInst, props) { export function getPropsValues(vueInst, props) {
return Object.keys(props) return Object.keys(props).reduce((acc, prop) => {
.reduce( if (vueInst[prop] !== undefined) {
(acc, prop) => { acc[prop] = vueInst[prop]
if (vueInst[prop] !== undefined) { }
acc[prop] = vueInst[prop] return acc
} }, {})
return acc
},
{}
)
} }
/** /**
* Binds the properties defined in props to the google maps instance. * Binds the properties defined in props to the google maps instance.
* If the prop is an Object type, and we wish to track the properties * If the prop is an Object type, and we wish to track the properties
* of the object (e.g. the lat and lng of a LatLng), then we do a deep * of the object (e.g. the lat and lng of a LatLng), then we do a deep
* watch. For deep watch, we also prevent the _changed event from being * watch. For deep watch, we also prevent the _changed event from being
* $emitted if the data source was external. * $emitted if the data source was external.
*/ */
export function bindProps (vueInst, googleMapsInst, props, options) { export function bindProps(vueInst, googleMapsInst, props) {
for (let attribute in props) { for (let attribute in props) {
let {twoWay, type, trackProperties, noBind} = props[attribute] let { twoWay, type, trackProperties, noBind } = props[attribute]
if (noBind) continue if (noBind) continue
@@ -36,7 +32,9 @@ export function bindProps (vueInst, googleMapsInst, props, options) {
const initialValue = vueInst[attribute] const initialValue = vueInst[attribute]
if (typeof googleMapsInst[setMethodName] === 'undefined') { if (typeof googleMapsInst[setMethodName] === 'undefined') {
throw new Error(`${setMethodName} is not a method of (the Maps object corresponding to) ${vueInst.$options._componentTag}`) throw new Error(
`${setMethodName} is not a method of (the Maps object corresponding to) ${vueInst.$options._componentTag}`
)
} }
// We need to avoid an endless // We need to avoid an endless
@@ -44,18 +42,22 @@ export function bindProps (vueInst, googleMapsInst, props, options) {
// although this may really be the user's responsibility // although this may really be the user's responsibility
if (type !== Object || !trackProperties) { if (type !== Object || !trackProperties) {
// Track the object deeply // Track the object deeply
vueInst.$watch(attribute, () => { vueInst.$watch(
const attributeValue = vueInst[attribute] attribute,
() => {
const attributeValue = vueInst[attribute]
googleMapsInst[setMethodName](attributeValue) googleMapsInst[setMethodName](attributeValue)
}, { },
immediate: typeof initialValue !== 'undefined', {
deep: type === Object immediate: typeof initialValue !== 'undefined',
}) deep: type === Object,
}
)
} else { } else {
WatchPrimitiveProperties( WatchPrimitiveProperties(
vueInst, vueInst,
trackProperties.map(prop => `${attribute}.${prop}`), trackProperties.map((prop) => `${attribute}.${prop}`),
() => { () => {
googleMapsInst[setMethodName](vueInst[attribute]) googleMapsInst[setMethodName](vueInst[attribute])
}, },
@@ -63,10 +65,9 @@ export function bindProps (vueInst, googleMapsInst, props, options) {
) )
} }
if (twoWay && if (twoWay && (vueInst.$gmapOptions.autobindAllEvents || vueInst.$attrs[eventName])) {
(vueInst.$gmapOptions.autobindAllEvents || googleMapsInst.addListener(eventName, () => {
vueInst.$attrs[eventName])) { // eslint-disable-line no-unused-vars
googleMapsInst.addListener(eventName, (ev) => { // eslint-disable-line no-unused-vars
vueInst.$emit(eventName, googleMapsInst[getMethodName]()) vueInst.$emit(eventName, googleMapsInst[getMethodName]())
}) })
} }

View File

@@ -1,7 +1,7 @@
// This piece of code was orignally written by sindresorhus and can be seen here // This piece of code was orignally written by sindresorhus and can be seen here
// https://github.com/sindresorhus/lazy-value/blob/master/index.js // https://github.com/sindresorhus/lazy-value/blob/master/index.js
export default fn => { export default (fn) => {
let called = false let called = false
let ret let ret

View File

@@ -10,13 +10,13 @@ operations so it exposes a property which accepts a bus
export default { export default {
props: ['resizeBus'], props: ['resizeBus'],
data () { data() {
return { return {
_actualResizeBus: null, _actualResizeBus: null,
} }
}, },
created () { created() {
if (typeof this.resizeBus === 'undefined') { if (typeof this.resizeBus === 'undefined') {
this.$data._actualResizeBus = this.$gmapDefaultResizeBus this.$data._actualResizeBus = this.$gmapDefaultResizeBus
} else { } else {
@@ -25,31 +25,32 @@ export default {
}, },
methods: { methods: {
_resizeCallback () { _resizeCallback() {
this.resize() this.resize()
}, },
_delayedResizeCallback () { _delayedResizeCallback() {
this.$nextTick(() => this._resizeCallback()) this.$nextTick(() => this._resizeCallback())
} },
}, },
watch: { watch: {
resizeBus (newVal, oldVal) { // eslint-disable-line no-unused-vars resizeBus(newVal) {
// eslint-disable-line no-unused-vars
this.$data._actualResizeBus = newVal this.$data._actualResizeBus = newVal
}, },
'$data._actualResizeBus' (newVal, oldVal) { '$data._actualResizeBus'(newVal, oldVal) {
if (oldVal) { if (oldVal) {
oldVal.$off('resize', this._delayedResizeCallback) oldVal.$off('resize', this._delayedResizeCallback)
} }
if (newVal) { if (newVal) {
// newVal.$on('resize', this._delayedResizeCallback) // newVal.$on('resize', this._delayedResizeCallback)
} }
} },
}, },
unmounted () { unmounted() {
if (this.$data._actualResizeBus) { if (this.$data._actualResizeBus) {
this.$data._actualResizeBus.$off('resize', this._delayedResizeCallback) this.$data._actualResizeBus.$off('resize', this._delayedResizeCallback)
} }
} },
} }

View File

@@ -2,9 +2,9 @@
// http://stackoverflow.com/a/11703018/2694653 // http://stackoverflow.com/a/11703018/2694653
// This has been ported to Vanilla.js by GuillaumeLeclerc // This has been ported to Vanilla.js by GuillaumeLeclerc
export default (input) => { export default (input) => {
var _addEventListener = (input.addEventListener) ? input.addEventListener : input.attachEvent var _addEventListener = input.addEventListener ? input.addEventListener : input.attachEvent
function addEventListenerWrapper (type, listener) { function addEventListenerWrapper(type, listener) {
// Simulate a 'down arrow' keypress on hitting 'return' when no pac suggestion is selected, // Simulate a 'down arrow' keypress on hitting 'return' when no pac suggestion is selected,
// and then trigger the original listener. // and then trigger the original listener.
if (type === 'keydown') { if (type === 'keydown') {

View File

@@ -1,53 +0,0 @@
/* vim: set softtabstop=2 shiftwidth=2 expandtab : */
const webpack = require('webpack');
const path = require('path')
const baseConfig = {
entry: [
path.resolve('./src/main.js')
],
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader',
options: { target: 'node' }
},
{
test: /\.js$/,
loader: 'babel-loader',
exclude: [
/node_modules/,
]
},
{
test: /\.(png|jpg|gif)$/,
use: [{
loader: 'file-loader?name=[name].[ext]?[hash]',
}]
},
],
},
mode: process.env.NODE_ENV || 'development'
}; /* baseConfig */
/**
* Web config uses a global Vue and Lodash object.
* */
const webConfig = {
...baseConfig,
externals: {
vue: 'Vue',
'marker-clusterer-plus': 'MarkerClusterer'
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: "vue-google-maps.js",
library: ["VueGoogleMaps"],
libraryTarget: "umd"
}
}
module.exports = [
webConfig
];