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 = {
root: true,
env: {
node: true
},
extends: [
"plugin:vue/vue3-essential",
"eslint:recommended",
"@vue/prettier",
"plugin:cypress/recommended"
],
parserOptions: {
parser: "babel-eslint"
},
rules: {
"no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
"no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off"
},
overrides: [
{
files: [
"**/__tests__/*.{j,t}s?(x)",
"**/tests/unit/**/*.spec.{j,t}s?(x)"
],
env: {
jest: true
}
}
]
};
module.exports = {
env: {
browser: true,
es6: true,
node: true,
},
"globals": {
"google": true
},
// the ts-eslint recommended ruleset sets the parser so we need to set it back
parser: 'vue-eslint-parser',
parserOptions: {
ecmaVersion: 2020,
extraFileExtensions: ['.vue'],
ecmaFeatures: {
jsx: true,
},
sourceType: 'module',
},
extends: [
'plugin:vue/vue3-essential',
'eslint:recommended',
'plugin:prettier/recommended',
'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
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",
"description": "Google Map components for Vue.js 3",
"version": "0.3.0",
"version": "0.5.0",
"private": false,
"main": "src/index.js",
"browserify": {
"transform": [
"babelify",
"vueify"
]
},
"bugs": {
"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": {
"babel-runtime": "^6.26.0",
"marker-clusterer-plus": "^2.1.4"
"@babel/runtime": "^7.12.13",
"core-js": "^3.8.3",
"vue": "^3.0.5"
},
"deprecated": false,
"devDependencies": {
"babel-cli": "^6.26.0",
"babel-core": "^6.26.0",
"babel-loader": "^7.1.3",
"babel-plugin-minify-dead-code-elimination": "^0.4.0",
"babel-plugin-transform-inline-environment-variables": "^0.4.0",
"babel-plugin-transform-object-rest-spread": "^6.26.0",
"babel-preset-env": "^1.6.1",
"babel-preset-es2015": "^6.24.1",
"babel-preset-stage-2": "^6.24.1",
"code": "^5.2.0",
"cross-env": "^1.0.8",
"css-loader": "^0.23.0",
"eslint": "^3.17.1",
"eslint-config-standard": "^7.0.1",
"eslint-plugin-html": "^2.0.1",
"eslint-plugin-promise": "^3.5.0",
"eslint-plugin-standard": "^2.1.1",
"file-loader": "^0.8.4",
"gh-pages": "^0.11.0",
"jsdom": "^9.8.3",
"json-loader": "^0.5.7",
"lab": "^15.3.0",
"less": "^2.5.3",
"less-loader": "^2.2.2",
"lodash": "^4.15.0",
"lodash-es": "^4.17.4",
"lodash-webpack-plugin": "^0.11.4",
"node-sass": "^4.9.4",
"puppeteer": "^1.1.0",
"raw-loader": "^0.5.1",
"sass-loader": "^7.0.1",
"shx": "^0.2.0",
"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"
"@babel/core": "^7.12.13",
"@babel/plugin-transform-runtime": "^7.12.15",
"@babel/preset-env": "^7.12.13",
"@vue/compiler-sfc": "^3.0.5",
"babel-loader": "^8.2.2",
"chalk": "^4.1.0",
"copy-webpack-plugin": "^7.0.0",
"css-loader": "^5.0.1",
"dotenv": "^8.2.0",
"dotenv-expand": "^5.1.0",
"eslint": "^7.19.0",
"eslint-config-prettier": "^7.2.0",
"eslint-formatter-friendly": "^7.0.0",
"eslint-plugin-prettier": "^3.3.1",
"eslint-plugin-vue": "^7.5.0",
"eslint-webpack-plugin": "^2.5.0",
"html-webpack-plugin": "^5.0.0",
"mini-css-extract-plugin": "^1.3.5",
"ora": "^5.3.0",
"postcss": "^8.2.4",
"postcss-loader": "^5.0.0",
"prettier": "2.2.1",
"rimraf": "^3.0.2",
"strip-ansi": "^6.0.0",
"style-loader": "^2.0.0",
"terser-webpack-plugin": "^5.1.1",
"thread-loader": "^3.0.1",
"vue-eslint-parser": "^7.4.1",
"vue-loader": "^16.1.2",
"vue-style-loader": "^4.1.2",
"webpack": "^5.20.2",
"webpack-merge": "^5.7.3"
},
"bundleDependencies": false,
"homepage": "https://vue-map.netlify.app/",
"license": "MIT",
"repository": {
"type": "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": [
[
"env",
{
"targets": {
"browsers": [
"last 2 versions",
"safari >= 7"
]
}
}
]
],
"presets": ["@babel/preset-env"],
"plugins": [
"transform-object-rest-spread",
"@babel/plugin-proposal-object-rest-spread",
"transform-inline-environment-variables",
"minify-dead-code-elimination"
]

View File

View File

@@ -1,11 +1,79 @@
<template>
<input
ref="input"
v-bind="$attrs"
v-on="$attrs"
/>
<input ref="input" v-bind="$attrs" v-on="$attrs" />
</template>
<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>

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 {mappedPropsToVueProps} from './mapElementFactory'
import { mappedPropsToVueProps } from './mapElementFactory'
const mappedProps = {
bounds: {
type: Object
type: Object,
},
componentRestrictions: {
type: Object,
@@ -16,7 +16,7 @@ const mappedProps = {
type: Array,
default: function () {
return []
}
},
},
}
@@ -24,34 +24,36 @@ const props = {
selectFirstOnEnter: {
required: false,
type: Boolean,
default: false
default: false,
},
options: {
type: Object
}
type: Object,
},
}
export default {
mounted () {
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?')
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.options,
}
this.$autocomplete = new google.maps.places.Autocomplete(this.$refs.input, finalOptions)
bindProps(this, this.$autocomplete, mappedProps)
this.$watch('componentRestrictions', v => {
this.$watch('componentRestrictions', (v) => {
if (v !== undefined) {
this.$autocomplete.setComponentRestrictions(v)
}
@@ -66,6 +68,6 @@ export default {
},
props: {
...mappedPropsToVueProps(mappedProps),
...props
}
...props,
},
}

View File

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

View File

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

View File

@@ -2,13 +2,90 @@
<template>
<div>
<div ref="flyaway"> <!-- so named because it will fly away to another component -->
<slot>
</slot>
<div ref="flyaway">
<!-- so named because it will fly away to another component -->
<slot> </slot>
</div>
</div>
</template>
<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>

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

View File

@@ -9,10 +9,10 @@
* */
export default {
inject: {
'$mapPromise': { default: 'abcdef' }
$mapPromise: { default: 'abcdef' },
},
provide () {
provide() {
// Note: although this mixin is not "providing" anything,
// components' expect the `$map` property to be present on the component.
// 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 {bindProps, getPropsValues} from '../utils/bindProps.js'
import { bindProps, getPropsValues } from '../utils/bindProps.js'
import mountableMixin from '../utils/mountableMixin.js'
import TwoWayBindingWrapper from '../utils/TwoWayBindingWrapper.js'
@@ -25,7 +25,7 @@ const props = {
},
mapTypeId: {
twoWay: true,
type: String
type: String,
},
tilt: {
twoWay: true,
@@ -33,8 +33,10 @@ const props = {
},
options: {
type: Object,
default () { return {} }
}
default() {
return {}
},
},
}
const events = [
@@ -54,27 +56,26 @@ const events = [
]
// Plain Google Maps methods exposed here for convenience
const linkedMethods = [
'panBy',
'panTo',
'panToBounds',
'fitBounds'
].reduce((all, methodName) => {
const linkedMethods = ['panBy', 'panTo', 'panToBounds', 'fitBounds'].reduce((all, methodName) => {
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
}, {})
// Other convenience methods exposed by Vue Google Maps
const customMethods = {
resize () {
resize() {
if (this.$mapObject) {
google.maps.event.trigger(this.$mapObject, 'resize')
}
},
resizePreserveCenter () {
if (!this.$mapObject) { return }
resizePreserveCenter() {
if (!this.$mapObject) {
return
}
const oldCenter = this.$mapObject.getCenter()
google.maps.event.trigger(this.$mapObject, 'resize')
@@ -84,98 +85,97 @@ const customMethods = {
/// Override mountableMixin::_resizeCallback
/// because resizePreserveCenter is usually the
/// expected behaviour
_resizeCallback () {
_resizeCallback() {
this.resizePreserveCenter()
}
},
}
export default {
mixins: [mountableMixin],
props: mappedPropsToVueProps(props),
provide () {
provide() {
this.$mapPromise = new Promise((resolve, reject) => {
this.$mapPromiseDeferred = { resolve, reject }
})
return {
'$mapPromise': this.$mapPromise
$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
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
finalLng() {
return this.center && typeof this.center.lng === 'function'
? this.center.lng()
: this.center.lng
},
finalLatLng() {
return { lat: this.finalLat, lng: this.finalLng }
},
finalLatLng () {
return {lat: this.finalLat, lng: this.finalLng}
}
},
watch: {
zoom (zoom) {
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']
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)
// 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)
// 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())
// 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)
}
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 = () => {
increment()
this.$mapObject.setCenter(this.finalLatLng)
}
this.$mapPromiseDeferred.resolve(this.$mapObject)
WatchPrimitiveProperties(
this,
['finalLat', 'finalLng'],
updateCenter
)
return this.$mapObject
})
this.$mapObject.addListener('zoom_changed', () => {
this.$emit('zoom_changed', this.$mapObject.getZoom())
.catch((error) => {
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: {
...customMethods,

View File

@@ -3,7 +3,7 @@ import mapElementFactory from './mapElementFactory.js'
const props = {
animation: {
twoWay: true,
type: Number
type: Number,
},
attribution: {
type: Object,
@@ -11,31 +11,30 @@ const props = {
clickable: {
type: Boolean,
twoWay: true,
default: true
default: true,
},
cursor: {
type: String,
twoWay: true
twoWay: true,
},
draggable: {
type: Boolean,
twoWay: true,
default: false
default: false,
},
icon: {
twoWay: true
},
label: {
twoWay: true,
},
label: {},
opacity: {
type: Number,
default: 1
default: 1,
},
options: {
type: Object
type: Object,
},
place: {
type: Object
type: Object,
},
position: {
type: Object,
@@ -43,15 +42,15 @@ const props = {
},
shape: {
type: Object,
twoWay: true
twoWay: true,
},
title: {
type: String,
twoWay: true
twoWay: true,
},
zIndex: {
type: Number,
twoWay: true
twoWay: true,
},
visible: {
twoWay: true,
@@ -69,7 +68,7 @@ const events = [
'mouseup',
'mousedown',
'mouseover',
'mouseout'
'mouseout',
]
/**
@@ -91,26 +90,26 @@ export default mapElementFactory({
ctr: () => google.maps.Marker,
inject: {
'$clusterPromise': {
$clusterPromise: {
default: null,
},
},
render (h) {
render(h) {
if (!this.$slots.default || this.$slots.default.length === 0) {
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]
} else {
return h(
'div',
this.$slots.default
)
return h('div', this.$slots.default)
}
},
emits: ['center_changed', 'zoom_changed', 'bounds_changed'],
unmounted () {
if (!this.$markerObject) { return }
unmounted() {
if (!this.$markerObject) {
return
}
if (this.$clusterObject) {
// Repaint will be performed in `updated()` of cluster
@@ -120,7 +119,7 @@ export default mapElementFactory({
}
},
beforeCreate (options) {
beforeCreate(options) {
if (this.$clusterPromise) {
options.map = null
}
@@ -128,7 +127,7 @@ export default mapElementFactory({
return this.$clusterPromise
},
afterCreate (inst) {
afterCreate(inst) {
if (this.$clusterPromise) {
this.$clusterPromise.then((co) => {
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 = {
draggable: {
type: Boolean
type: Boolean,
},
editable: {
type: Boolean,
},
options: {
type: Object
type: Object,
},
path: {
type: Array,
@@ -33,88 +33,98 @@ const events = [
'mouseout',
'mouseover',
'mouseup',
'rightclick'
'rightclick',
]
export default mapElementFactory({
props: {
deepWatch: {
type: Boolean,
default: false
}
default: false,
},
},
events,
mappedProps: props,
name: 'polygon',
ctr: () => google.maps.Polygon,
beforeCreate (options) {
beforeCreate(options) {
if (!options.path) delete options.path
if (!options.paths) delete options.paths
},
afterCreate (inst) {
afterCreate(inst) {
var clearEvents = () => {}
// Watch paths, on our own, because we do not want to set either when it is
// empty
this.$watch('paths', (paths) => {
if (paths) {
clearEvents()
this.$watch(
'paths',
(paths) => {
if (paths) {
clearEvents()
inst.setPaths(paths)
inst.setPaths(paths)
const updatePaths = () => {
this.$emit('paths_changed', inst.getPaths())
const updatePaths = () => {
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('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))
clearEvents = () => {
eventListeners.map((
[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 = {
draggable: {
type: Boolean
type: Boolean,
},
editable: {
type: Boolean,
},
options: {
twoWay: false,
type: Object
type: Object,
},
path: {
type: Array,
twoWay: true
twoWay: true,
},
}
@@ -28,7 +28,7 @@ const events = [
'mouseout',
'mouseover',
'mouseup',
'rightclick'
'rightclick',
]
export default mapElementFactory({
@@ -37,41 +37,46 @@ export default mapElementFactory({
deepWatch: {
type: Boolean,
default: false,
}
},
},
events,
name: 'polyline',
ctr: () => google.maps.Polyline,
afterCreate (inst) {
afterCreate() {
var clearEvents = () => {}
this.$watch('path', (path) => {
if (path) {
clearEvents()
this.$watch(
'path',
(path) => {
if (path) {
clearEvents()
this.$polylineObject.setPath(path)
this.$polylineObject.setPath(path)
const mvcPath = this.$polylineObject.getPath()
const eventListeners = []
const mvcPath = this.$polylineObject.getPath()
const eventListeners = []
const updatePaths = () => {
this.$emit('path_changed', this.$polylineObject.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))
const updatePaths = () => {
this.$emit('path_changed', this.$polylineObject.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,
}
}, {
deep: this.deepWatch,
immediate: true,
})
}
)
},
})

View File

@@ -3,7 +3,7 @@ import mapElementFactory from './mapElementFactory.js'
const props = {
bounds: {
type: Object,
twoWay: true
twoWay: true,
},
draggable: {
type: Boolean,
@@ -15,8 +15,8 @@ const props = {
},
options: {
type: Object,
twoWay: false
}
twoWay: false,
},
}
const events = [
@@ -30,7 +30,7 @@ const events = [
'mouseout',
'mouseover',
'mouseup',
'rightclick'
'rightclick',
]
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 {loadGmapApi} from './manager'
import {createApp} from 'vue'
import { loadGmapApi } from './manager'
import { createApp } from 'vue'
import Marker from './components/marker'
import Polyline from './components/polyline'
import Polygon from './components/polygon'
import Circle from './components/circle'
import Rectangle from './components/rectangle'
import GmapCluster from './components/cluster'
// Vue component imports
import GmapCluster from './components/cluster.vue'
import InfoWindow from './components/infoWindow.vue'
import Map from './components/map.vue'
import StreetViewPanorama from './components/streetViewPanorama.vue'
import PlaceInput from './components/placeInput.vue'
import Autocomplete from './components/autocomplete.vue'
import MapElementMixin from './components/mapElementMixin'
import MapElementFactory from './components/mapElementFactory'
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
// export everything
export {loadGmapApi, Marker, Polyline, Polygon, Circle, Cluster, Rectangle,
InfoWindow, Map, PlaceInput, MapElementMixin, MapElementFactory, Autocomplete,
MountableMixin, StreetViewPanorama}
export {
loadGmapApi,
Marker,
Polyline,
Polygon,
Circle,
GmapCluster,
Rectangle,
InfoWindow,
Map,
MapElementMixin,
MapElementFactory,
Autocomplete,
MountableMixin,
}
export function install (Vue, options) {
export function install(Vue, options) {
// Set defaults
options = {
installComponents: true,
autobindAllEvents: false,
...options
...options,
}
// Update the global `GmapApi`. This will allow
@@ -49,7 +46,11 @@ export function install (Vue, options) {
// via:
// import {gmapApi} from 'vue2-google-maps'
// export default { computed: { google: gmapApi } }
GmapApi = createApp({data: {gmapApi: null}});
GmapApi = createApp({
data: function () {
return { gmapApi: null }
},
})
const defaultResizeBus = createApp()
@@ -58,11 +59,11 @@ export function install (Vue, options) {
let gmapApiPromiseLazy = makeGmapApiPromiseLazy(options)
Vue.mixin({
created () {
created() {
this.$gmapDefaultResizeBus = defaultResizeBus
this.$gmapOptions = options
this.$gmapApiPromiseLazy = gmapApiPromiseLazy
}
},
})
Vue.$gmapDefaultResizeBus = defaultResizeBus
Vue.$gmapApiPromiseLazy = gmapApiPromiseLazy
@@ -77,22 +78,23 @@ export function install (Vue, options) {
Vue.component('GmapCircle', Circle)
Vue.component('GmapRectangle', Rectangle)
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
function onApiLoaded () {
function onApiLoaded() {
GmapApi.gmapApi = {}
return window.google
}
if (options.load) { // If library should load the API
return lazy(() => { // Load the
if (options.load) {
// If library should load the API
return lazy(() => {
// Load the
// 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)
} else {
return new Promise((resolve, reject) => {
@@ -102,11 +104,11 @@ function makeGmapApiPromiseLazy (options) {
} catch (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`
// when the Google Maps API has been loaded
const promise = new Promise((resolve) => {
@@ -121,6 +123,6 @@ function makeGmapApiPromiseLazy (options) {
}
}
export function gmapApi () {
export function gmapApi() {
return GmapApi.gmapApi && window.google
}

View File

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

View File

@@ -1,48 +1,52 @@
/**
* 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
* 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
* the event when the counter is zero.
*
Example:
Let's say DrawingRecognitionCanvas is a deep-learning backed canvas
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
of the image by way of the @newObjectRecognized event.
<input
type="text"
placeholder="an object, e.g. Dog, Cat, Frog"
v-model="identifiedObject" />
<DrawingRecognitionCanvas
:object="identifiedObject"
@newObjectRecognized="identifiedObject = $event"
/>
new TwoWayBindingWrapper((increment, decrement, shouldUpdate) => {
this.$watch('identifiedObject', () => {
// new object passed in
increment()
})
this.$deepLearningBackend.on('drawingChanged', () => {
recognizeObject(this.$deepLearningBackend)
.then((object) => {
decrement()
if (shouldUpdate()) {
this.$emit('newObjectRecognized', object.name)
}
})
})
})
*/
export default function TwoWayBindingWrapper (fn) {
let counter = 0
fn(
() => { counter += 1 },
() => { counter = Math.max(0, counter - 1) },
() => counter === 0,
)
}
/**
* 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
* 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
* the event when the counter is zero.
*
Example:
Let's say DrawingRecognitionCanvas is a deep-learning backed canvas
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
of the image by way of the @newObjectRecognized event.
<input
type="text"
placeholder="an object, e.g. Dog, Cat, Frog"
v-model="identifiedObject" />
<DrawingRecognitionCanvas
:object="identifiedObject"
@newObjectRecognized="identifiedObject = $event"
/>
new TwoWayBindingWrapper((increment, decrement, shouldUpdate) => {
this.$watch('identifiedObject', () => {
// new object passed in
increment()
})
this.$deepLearningBackend.on('drawingChanged', () => {
recognizeObject(this.$deepLearningBackend)
.then((object) => {
decrement()
if (shouldUpdate()) {
this.$emit('newObjectRecognized', object.name)
}
})
})
})
*/
export default function TwoWayBindingWrapper(fn) {
let counter = 0
fn(
() => {
counter += 1
},
() => {
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
* per se. This is different from a deep watch where both the reference
* and the individual values are watched.
*
* In effect, it throttles the multiple $watch to execute at most once per tick.
*/
export default function WatchPrimitiveProperties (vueInst, propertiesToTrack, handler, immediate = false) {
let isHandled = false
function requestHandle () {
if (!isHandled) {
isHandled = true
vueInst.$nextTick(() => {
isHandled = false
handler()
})
}
}
for (let prop of propertiesToTrack) {
vueInst.$watch(prop, requestHandle, {immediate})
}
}
/**
* 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
* and the individual values are watched.
*
* In effect, it throttles the multiple $watch to execute at most once per tick.
*/
export default function WatchPrimitiveProperties(
vueInst,
propertiesToTrack,
handler,
immediate = false
) {
let isHandled = false
function requestHandle() {
if (!isHandled) {
isHandled = true
vueInst.$nextTick(() => {
isHandled = false
handler()
})
}
}
for (let prop of propertiesToTrack) {
vueInst.$watch(prop, requestHandle, { immediate })
}
}

View File

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

View File

@@ -1,32 +1,28 @@
import WatchPrimitiveProperties from '../utils/WatchPrimitiveProperties'
function capitalizeFirstLetter (string) {
function capitalizeFirstLetter(string) {
return string.charAt(0).toUpperCase() + string.slice(1)
}
export function getPropsValues (vueInst, props) {
return Object.keys(props)
.reduce(
(acc, prop) => {
if (vueInst[prop] !== undefined) {
acc[prop] = vueInst[prop]
}
return acc
},
{}
)
export function getPropsValues(vueInst, props) {
return Object.keys(props).reduce((acc, prop) => {
if (vueInst[prop] !== undefined) {
acc[prop] = vueInst[prop]
}
return acc
}, {})
}
/**
* 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
* 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
* $emitted if the data source was external.
*/
export function bindProps (vueInst, googleMapsInst, props, options) {
* 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
* 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
* $emitted if the data source was external.
*/
export function bindProps(vueInst, googleMapsInst, props) {
for (let attribute in props) {
let {twoWay, type, trackProperties, noBind} = props[attribute]
let { twoWay, type, trackProperties, noBind } = props[attribute]
if (noBind) continue
@@ -36,7 +32,9 @@ export function bindProps (vueInst, googleMapsInst, props, options) {
const initialValue = vueInst[attribute]
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
@@ -44,18 +42,22 @@ export function bindProps (vueInst, googleMapsInst, props, options) {
// although this may really be the user's responsibility
if (type !== Object || !trackProperties) {
// Track the object deeply
vueInst.$watch(attribute, () => {
const attributeValue = vueInst[attribute]
vueInst.$watch(
attribute,
() => {
const attributeValue = vueInst[attribute]
googleMapsInst[setMethodName](attributeValue)
}, {
immediate: typeof initialValue !== 'undefined',
deep: type === Object
})
googleMapsInst[setMethodName](attributeValue)
},
{
immediate: typeof initialValue !== 'undefined',
deep: type === Object,
}
)
} else {
WatchPrimitiveProperties(
vueInst,
trackProperties.map(prop => `${attribute}.${prop}`),
trackProperties.map((prop) => `${attribute}.${prop}`),
() => {
googleMapsInst[setMethodName](vueInst[attribute])
},
@@ -63,10 +65,9 @@ export function bindProps (vueInst, googleMapsInst, props, options) {
)
}
if (twoWay &&
(vueInst.$gmapOptions.autobindAllEvents ||
vueInst.$attrs[eventName])) {
googleMapsInst.addListener(eventName, (ev) => { // eslint-disable-line no-unused-vars
if (twoWay && (vueInst.$gmapOptions.autobindAllEvents || vueInst.$attrs[eventName])) {
googleMapsInst.addListener(eventName, () => {
// eslint-disable-line no-unused-vars
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
// https://github.com/sindresorhus/lazy-value/blob/master/index.js
export default fn => {
export default (fn) => {
let called = false
let ret

View File

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

View File

@@ -2,9 +2,9 @@
// http://stackoverflow.com/a/11703018/2694653
// This has been ported to Vanilla.js by GuillaumeLeclerc
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,
// and then trigger the original listener.
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
];