diff --git a/.babelrc b/.babelrc new file mode 100755 index 0000000..6cfa7b9 --- /dev/null +++ b/.babelrc @@ -0,0 +1,6 @@ +{ + "plugins": [ + "transform-react-jsx", + "syntax-jsx" + ] +} diff --git a/.editorconfig b/.editorconfig new file mode 100755 index 0000000..273bdd3 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,10 @@ +# http://editorconfig.org +root = true + +[*] +end_of_line = lf +insert_final_newline = true + +[*.{json,js}] +indent_style = space +indent_size = 2 diff --git a/.gitignore b/.gitignore new file mode 100755 index 0000000..8536d1b --- /dev/null +++ b/.gitignore @@ -0,0 +1,46 @@ +# Logs +logs +*.log +npm-debug.log* + +# Runtime data +pids +*.pid +*.seed + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules +jspm_packages + +# Optional npm cache directory +.npm + +# Optional REPL history +.node_repl_history + +# Webstorm local folder +.idea + +# Yarn dependecies lock file +yarn.lock + +# Application local files +dist diff --git a/.npmignore b/.npmignore new file mode 100755 index 0000000..a8cd5e3 --- /dev/null +++ b/.npmignore @@ -0,0 +1,6 @@ +src +.babelrc +node_modules +scripts +screenshot.png +webpack.config.js diff --git a/Readme.md b/Readme.md new file mode 100755 index 0000000..0ad795b --- /dev/null +++ b/Readme.md @@ -0,0 +1,22 @@ +# cerebro-google-maps + +> [Cerebro](http://www.cerebroapp.com) plugin quick search of location on google maps + +![](screenshot.png) + +## Usage + +Just enter what you want to convert! + +## Features +* Supports names of locations and addresses; +* Supports english and russian language (i.e. `карта москвы` or `new york on map`). + +## Related + +- [Cerebro](http://github.com/KELiON/cerebro) – main repo for Cerebro app; +- [cerebro-plugin](http://github.com/KELiON/cerebro-plugin) – boilerplate to create Cerebro plugins; + +## License + +MIT © [Alexandr Subbotin](http://asubbotin.ru) diff --git a/package.json b/package.json new file mode 100644 index 0000000..c1b0488 --- /dev/null +++ b/package.json @@ -0,0 +1,46 @@ +{ + "name": "cerebro-google-maps", + "version": "1.0.0", + "description": "Cerebro plugin for google maps search", + "license": "MIT", + "repository": "KELiON/cerebro-google-maps", + "author": { + "name": "Alexandr Subbotin", + "email": "kelionweb@gmail.com", + "url": "asubbotin.ru" + }, + "engines": { + "node": ">=4" + }, + "scripts": { + "build": "webpack && babili dist -d dist --compact --no-comments", + "debug": "./scripts/debug", + "debug:windows": "scripts\\debug.bat", + "prepublish": "rimraf ./dist && npm run build" + }, + "main": "dist/index.js", + "keywords": [ + "cerebro", + "cerebro-plugin", + "maps", + "search" + ], + "devDependencies": { + "babel-cli": "^6.18.0", + "babel-loader": "^6.2.8", + "babel-plugin-syntax-jsx": "^6.18.0", + "babel-plugin-transform-react-jsx": "^6.8.0", + "babili": "0.0.9", + "cerebro-tools": "^0.1.0", + "css-loader": "^0.26.0", + "react": "15.4.2", + "rimraf": "^2.6.1", + "style-loader": "^0.13.1", + "url-loader": "^0.5.7", + "webpack": "2.1.0-beta.27" + }, + "dependencies": { + "react-dom": "15.4.2", + "react-google-maps": "4.11.0" + } +} diff --git a/screenshot.png b/screenshot.png new file mode 100644 index 0000000..8f8177d Binary files /dev/null and b/screenshot.png differ diff --git a/scripts/debug b/scripts/debug new file mode 100755 index 0000000..6d7be8f --- /dev/null +++ b/scripts/debug @@ -0,0 +1,31 @@ +#!/bin/sh +if [[ $1 == "dev" ]]; then + appname="Electron" +else + appname="Cerebro" +fi + +case "$(uname -s)" in + + Darwin) + symlink="${HOME}/Library/Application Support/${appname}/plugins/node_modules/${PWD##*/}" + ;; + + Linux) + symlink="${HOME}/.config/${appname}/plugins/node_modules/${PWD##*/}" + ;; + + CYGWIN*|MINGW32*|MINGW64*|MSYS*) + symlink="${APPDATA}\\${appname}\plugins\node_modules\\${PWD##*/}" + ;; + + *) + echo "Unknown system. Please, open an issue in https://github.com/KELiON/cerebro-plugin/issues" + exit + ;; +esac + +echo "Creating symlink: $symlink -> ${PWD}" +ln -s "${PWD}" "$symlink" +# trap "echo 'Deleting symlink' && rm -rf \"$symlink\"" SIGHUP SIGINT SIGTERM +# ./node_modules/.bin/webpack --watch diff --git a/scripts/debug.bat b/scripts/debug.bat new file mode 100755 index 0000000..e8438af --- /dev/null +++ b/scripts/debug.bat @@ -0,0 +1,14 @@ +@echo off +IF "%1%"==""dev"" ( + set appname=Electron +) ELSE ( + set appname=Cerebro +) + +for %%* in (.) do set dirname=%%~nx* +SET symlink="%APPDATA%\%appname%\plugins\node_modules\%dirname%" + +echo "Creating symlink: %symlink% -> %cd%" + +mklink /d "%symlink%" "%cd%" +start "" /wait node_modules\.bin\webpack --watch diff --git a/src/Preview/index.js b/src/Preview/index.js new file mode 100644 index 0000000..da88047 --- /dev/null +++ b/src/Preview/index.js @@ -0,0 +1,67 @@ +const React = require('react') +const GoogleMap = require('react-google-maps/lib/GoogleMap') +// eslint-disable-next-line no-unused-vars +const GoogleMapLoader = require('react-google-maps/lib/GoogleMapLoader') +const ScriptjsLoader = require('react-google-maps/lib/async/ScriptjsLoader') +const { triggerEvent } = require('react-google-maps/lib/utils') +const Marker = require('react-google-maps/lib/Marker') +const styles = require('./styles.css') + +class Preview extends React.Component { + /** + * Fit google maps to geocoded viewport + * + * @param {GoogleMap} map Ref to GoogleMap component + */ + fitBounds() { + const { map } = this + if (!map) return + // eslint-disable-next-line no-undef + const bounds = new google.maps.LatLngBounds() + bounds.extend(this.props.geometry.location) + const { viewport } = this.props.geometry + if (viewport) { + Object.keys(viewport).forEach(key => bounds.extend(viewport[key])) + } + map.fitBounds(bounds) + } + render() { + const { location } = this.props.geometry + const { name } = this.props + + const marker = { + position: location, + key: name + } + + return ( + } + containerElement={
} + googleMapElement={ + (this.map = map) && this.fitBounds()} + defaultZoom={3} + defaultCenter={location} + > + + + } + /> + ) + } +} + +Preview.propTypes = { + name: React.PropTypes.string, + geometry: React.PropTypes.shape({ + location: React.PropTypes.object, + viewport: React.PropTypes.object + }) +} + +module.exports = Preview diff --git a/src/Preview/styles.css b/src/Preview/styles.css new file mode 100644 index 0000000..6cab318 --- /dev/null +++ b/src/Preview/styles.css @@ -0,0 +1,7 @@ +.container { + position: absolute; + left: 0; + right: 0; + top: 0; + bottom: 0; +} diff --git a/src/geocode.js b/src/geocode.js new file mode 100644 index 0000000..2c581ba --- /dev/null +++ b/src/geocode.js @@ -0,0 +1,10 @@ +const { memoize } = require('cerebro-tools') + +const geocode = (term, userLang) => { + const url = `http://maps.googleapis.com/maps/api/geocode/json?address=${encodeURIComponent(term)}&language=${userLang}` + return fetch(url) + .then(response => response.json()) + .then(json => json.results) +} + +module.exports = memoize(geocode) diff --git a/src/getPlaces.js b/src/getPlaces.js new file mode 100644 index 0000000..94f02e8 --- /dev/null +++ b/src/getPlaces.js @@ -0,0 +1,15 @@ +const { memoize } = require('cerebro-tools') + +// Expire places cache in 30 minutes +const EXPIRATION = 30 * 60 * 1000 + +const getPlaces = ({ keyword, location, lang }) => { + const url = `https://maps.googleapis.com/maps/api/place/textsearch/json?key=AIzaSyA-O4pwGMszqqnKslBjG1ADC1Ol7HCyBd8&query=${encodeURIComponent(keyword)}&language=${lang}&location=${location}` + return fetch(url) + .then(response => response.json()) + .then(json => json.results) +} + +module.exports = memoize(getPlaces, { + maxAge: EXPIRATION +}) diff --git a/src/getUserLocation.js b/src/getUserLocation.js new file mode 100644 index 0000000..0352d39 --- /dev/null +++ b/src/getUserLocation.js @@ -0,0 +1,14 @@ +const { memoize } = require('cerebro-tools') + +// Expire geoip information in 30 minutes +const EXPIRATION = 30 * 60 * 1000 + +const getUserLocation = () => ( + fetch('http://freegeoip.net/json/') + .then(response => response.json()) + .then(json => `${json.latitude},${json.longitude}`) +) + +module.exports = memoize(getUserLocation, { + maxAge: EXPIRATION +}) diff --git a/src/icon.png b/src/icon.png new file mode 100644 index 0000000..23fc375 Binary files /dev/null and b/src/icon.png differ diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..5b99777 --- /dev/null +++ b/src/index.js @@ -0,0 +1,58 @@ +/* eslint camelcase:0 */ +const React = require('react') +const Preview = require('./Preview') +const geocode = require('./geocode') +const getPlaces = require('./getPlaces') +const getUserLocation = require('./getUserLocation') + +const icon = require('./icon.png') + +const toResultFn = (address, openUrl) => ({ name, geometry, formatted_address, place_id }) => ({ + icon, + id: place_id, + title: name, + subtitle: formatted_address, + term: formatted_address, + onSelect: () => { + const q = encodeURIComponent(address) + openUrl(`https://maps.google.com/?q=${q}`) + }, + getPreview: () => +}) + +/** + * Plugin to search & display google maps + * + * @param {String} options.term + * @param {Object} options.actions + * @param {Function} options.display + */ +const fn = ({ term, actions, display, config }) => { + let match = term.match(/^(?:maps?|карт(?:а|ы))\s+(.+)/i) + match = match || term.match(/(.+)\s(?:maps?|карт(?:а|ы))$/i) + if (!match) return + const address = match[1] + const lang = config.get('lang') + const toResult = toResultFn(address, actions.open) + getUserLocation().then(location => ( + getPlaces({ + location, + lang, + keyword: address + }) + )).then(points => { + const result = points.map(toResult) + display(result) + }) + geocode(address, lang).then(points => { + const result = points.map(toResult) + display(result) + }) +} + +module.exports = { + icon, + fn, + name: 'Search on google maps', + keyword: 'map', +} diff --git a/webpack.config.js b/webpack.config.js new file mode 100755 index 0000000..da34beb --- /dev/null +++ b/webpack.config.js @@ -0,0 +1,45 @@ +const webpack = require('webpack'); +const path = require('path'); + +module.exports = { + entry: { + index: './src/index' + }, + output: { + path: './dist', + libraryTarget: 'commonjs2', + filename: 'index.js' + }, + resolve: { + extensions: ['.js'], + modules: [ + path.resolve('./src'), + path.resolve('./node_modules'), + ] + }, + target: 'electron-renderer', + module: { + rules: [{ + test: /\.jsx?$/, + use: { + loader: 'babel-loader' + }, + exclude: /node_modules/ + }, { + test: /\.css$/, + use: [{ + loader: 'style-loader' + }, { + loader: 'css-loader', + query: { + modules: true + } + }] + }, { + test: /\.png$/, + use: { + loader: 'url-loader' + } + }] + } +};