diff --git a/package-lock.json b/package-lock.json index a728cca..6edb20c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1310,6 +1310,68 @@ "@types/yargs": "^13.0.0" } }, + "@mapbox/geojson-area": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@mapbox/geojson-area/-/geojson-area-0.2.2.tgz", + "integrity": "sha1-GNeBSqNr8j+7zDefjiaiKSfevxA=", + "requires": { + "wgs84": "0.0.0" + } + }, + "@mapbox/geojson-rewind": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@mapbox/geojson-rewind/-/geojson-rewind-0.4.0.tgz", + "integrity": "sha512-b+1uPWBERW4Pet/969BNu61ZPDyH2ilIxBjJDFzxyS9TyszF9UrTQyYIl/G38clux3rtpAGGFSGTCSF/qR6UjA==", + "requires": { + "@mapbox/geojson-area": "0.2.2", + "concat-stream": "~1.6.0", + "minimist": "1.2.0", + "sharkdown": "^0.1.0" + } + }, + "@mapbox/geojson-types": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@mapbox/geojson-types/-/geojson-types-1.0.2.tgz", + "integrity": "sha512-e9EBqHHv3EORHrSfbR9DqecPNn+AmuAoQxV6aL8Xu30bJMJR1o8PZLZzpk1Wq7/NfCbuhmakHTPYRhoqLsXRnw==" + }, + "@mapbox/jsonlint-lines-primitives": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@mapbox/jsonlint-lines-primitives/-/jsonlint-lines-primitives-2.0.2.tgz", + "integrity": "sha1-zlblOfg1UrWNENZy6k1vya3HsjQ=" + }, + "@mapbox/mapbox-gl-supported": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@mapbox/mapbox-gl-supported/-/mapbox-gl-supported-1.4.1.tgz", + "integrity": "sha512-yyKza9S6z3ELKuf6w5n6VNUB0Osu6Z93RXPfMHLIlNWohu3KqxewLOq4lMXseYJ92GwkRAxd207Pr/Z98cwmvw==" + }, + "@mapbox/point-geometry": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@mapbox/point-geometry/-/point-geometry-0.1.0.tgz", + "integrity": "sha1-ioP5M1x4YO/6Lu7KJUMyqgru2PI=" + }, + "@mapbox/tiny-sdf": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@mapbox/tiny-sdf/-/tiny-sdf-1.1.1.tgz", + "integrity": "sha512-Ihn1nZcGIswJ5XGbgFAvVumOgWpvIjBX9jiRlIl46uQG9vJOF51ViBYHF95rEZupuyQbEmhLaDPLQlU7fUTsBg==" + }, + "@mapbox/unitbezier": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/@mapbox/unitbezier/-/unitbezier-0.0.0.tgz", + "integrity": "sha1-FWUb1VOme4WB+zmIEMmK2Go0Uk4=" + }, + "@mapbox/vector-tile": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@mapbox/vector-tile/-/vector-tile-1.3.1.tgz", + "integrity": "sha512-MCEddb8u44/xfQ3oD+Srl/tNcQoqTw3goGk2oLsrFxOTc3dUp+kAnby3PvAeeBYSMSjSPD1nd1AJA6W49WnoUw==", + "requires": { + "@mapbox/point-geometry": "~0.1.0" + } + }, + "@mapbox/whoots-js": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@mapbox/whoots-js/-/whoots-js-3.1.0.tgz", + "integrity": "sha512-Es6WcD0nO5l+2BOQS4uLfNPYQaNDfbot3X1XUoloz+x0mPDS3eeORZJl06HXjwBG1fOGwCRnzK88LMdxKRrd6Q==" + }, "@mrmlnc/readdir-enhanced": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz", @@ -1892,6 +1954,11 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" }, + "ansicolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/ansicolors/-/ansicolors-0.2.1.tgz", + "integrity": "sha1-vgiVmQl7dKXJxKhKDNvNtivYeu8=" + }, "anymatch": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", @@ -2874,6 +2941,15 @@ "rsvp": "^4.8.4" } }, + "cardinal": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/cardinal/-/cardinal-0.4.4.tgz", + "integrity": "sha1-ylu2iltRG5D+k7ms6km97lwyv+I=", + "requires": { + "ansicolors": "~0.2.1", + "redeyed": "~0.4.0" + } + }, "case-sensitive-paths-webpack-plugin": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.2.0.tgz", @@ -4039,6 +4115,11 @@ "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz", "integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==" }, + "csscolorparser": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/csscolorparser/-/csscolorparser-1.0.3.tgz", + "integrity": "sha1-s085HupNqPPpgjHizNjfnAQfFxs=" + }, "cssdb": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-4.4.0.tgz", @@ -4583,6 +4664,11 @@ "stream-shift": "^1.0.0" } }, + "earcut": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/earcut/-/earcut-2.2.1.tgz", + "integrity": "sha512-5jIMi2RB3HtGPHcYd9Yyl0cczo84y+48lgKPxMijliNQaKAHEZJbdzLmKmdxG/mCdS/YD9DQ1gihL8mxzR0F9w==" + }, "ecc-jsbn": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", @@ -5990,6 +6076,11 @@ "globule": "^1.0.0" } }, + "geojson-vt": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/geojson-vt/-/geojson-vt-3.2.1.tgz", + "integrity": "sha512-EvGQQi/zPrDA6zr6BnJD/YhwAkBP8nnJ9emh3EnHQKVMfg/MRVtPbMYdgVy/IaEmn4UfagD2a6fafPDL5hbtwg==" + }, "get-caller-file": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", @@ -6066,6 +6157,11 @@ } } }, + "gl-matrix": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.1.0.tgz", + "integrity": "sha512-526NA+3EA+ztAQi0IZpSWiM0fyQXIp7IbRvfJ4wS/TjjQD0uv0fVybXwwqqSOlq33UckivI0yMDlVtboWm3k7A==" + }, "glob": { "version": "7.1.4", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", @@ -6168,6 +6264,11 @@ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.2.tgz", "integrity": "sha512-IItsdsea19BoLC7ELy13q1iJFNmd7ofZH5+X/pJr90/nRoPEX0DJo1dHDbgtYWOhJhcCgMDTOw84RZ72q6lB+Q==" }, + "grid-index": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/grid-index/-/grid-index-1.1.0.tgz", + "integrity": "sha512-HZRwumpOGUrHyxO5bqKZL0B0GlUpwtCAzZ42sgxUPniu33R1LSFH5yrIcBCHjkctCAh3mtWKcKd9J4vDDdeVHA==" + }, "growly": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", @@ -8969,6 +9070,11 @@ "object.assign": "^4.1.0" } }, + "kdbush": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/kdbush/-/kdbush-3.0.0.tgz", + "integrity": "sha512-hRkd6/XW4HTsA9vjVpY9tuXJYLSlelnkTmVFu4M9/7MIYQtFcHpbugAU7UbOfjOiVSVYl2fqgBuJ32JUmRo5Ew==" + }, "killable": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz", @@ -9009,11 +9115,6 @@ "invert-kv": "^1.0.0" } }, - "leaflet": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.5.1.tgz", - "integrity": "sha512-ekM9KAeG99tYisNBg0IzEywAlp0hYI5XRipsqRXyRTeuU8jcuntilpp+eFf5gaE0xubc9RuSNIVtByEKwqFV0w==" - }, "left-pad": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz", @@ -9258,6 +9359,43 @@ "object-visit": "^1.0.0" } }, + "mapbox-gl": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/mapbox-gl/-/mapbox-gl-1.4.0.tgz", + "integrity": "sha512-4nXRXanISou8oWLU7DH1ZetKvCJR9XtnMlQQ4Ia80wjghIHc5ljmAV/loNCI2UAGyuKINc7QcTiwUXrjE+Kv4w==", + "requires": { + "@mapbox/geojson-rewind": "^0.4.0", + "@mapbox/geojson-types": "^1.0.2", + "@mapbox/jsonlint-lines-primitives": "^2.0.2", + "@mapbox/mapbox-gl-supported": "^1.4.0", + "@mapbox/point-geometry": "^0.1.0", + "@mapbox/tiny-sdf": "^1.1.0", + "@mapbox/unitbezier": "^0.0.0", + "@mapbox/vector-tile": "^1.3.1", + "@mapbox/whoots-js": "^3.1.0", + "csscolorparser": "~1.0.2", + "earcut": "^2.2.0", + "geojson-vt": "^3.2.1", + "gl-matrix": "^3.0.0", + "grid-index": "^1.1.0", + "minimist": "0.0.8", + "murmurhash-js": "^1.0.0", + "pbf": "^3.0.5", + "potpack": "^1.0.1", + "quickselect": "^2.0.0", + "rw": "^1.3.3", + "supercluster": "^6.0.1", + "tinyqueue": "^2.0.0", + "vt-pbf": "^3.1.1" + }, + "dependencies": { + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + } + } + }, "md5.js": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", @@ -9552,6 +9690,11 @@ "resolved": "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz", "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=" }, + "murmurhash-js": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/murmurhash-js/-/murmurhash-js-1.0.0.tgz", + "integrity": "sha1-sGJ44h/Gw3+lMTcysEEry2rhX1E=" + }, "mute-stream": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", @@ -10267,6 +10410,15 @@ "pinkie-promise": "^2.0.0" } }, + "pbf": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/pbf/-/pbf-3.2.0.tgz", + "integrity": "sha512-98Eh7rsJNJF/Im6XYMLaOW3cLnNyedlOd6hu3iWMD5I7FZGgpw8yN3vQBrmLbLodu7G784Irb9Qsv2yFrxSAGw==", + "requires": { + "ieee754": "^1.1.12", + "resolve-protobuf-schema": "^2.1.0" + } + }, "pbkdf2": { "version": "3.0.17", "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.17.tgz", @@ -11274,6 +11426,11 @@ "uniq": "^1.0.1" } }, + "potpack": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/potpack/-/potpack-1.0.1.tgz", + "integrity": "sha512-15vItUAbViaYrmaB/Pbw7z6qX2xENbFSTA7Ii4tgbPtasxm5v6ryKhKtL91tpWovDJzTiZqdwzhcFBCwiMVdVw==" + }, "prelude-ls": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", @@ -11377,6 +11534,11 @@ "react-is": "^16.8.1" } }, + "protocol-buffers-schema": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.3.2.tgz", + "integrity": "sha512-Xdayp8sB/mU+sUV4G7ws8xtYMGdQnxbeIfLjyO9TZZRJdztBGhlmbI5x1qcY4TG5hBkIKGnc28i7nXxaugu88w==" + }, "proxy-addr": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.5.tgz", @@ -11484,6 +11646,11 @@ "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.1.1.tgz", "integrity": "sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==" }, + "quickselect": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-2.0.0.tgz", + "integrity": "sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw==" + }, "raf": { "version": "3.4.1", "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", @@ -11871,6 +12038,21 @@ "strip-indent": "^1.0.1" } }, + "redeyed": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/redeyed/-/redeyed-0.4.4.tgz", + "integrity": "sha1-N+mQpvKyGyoRwuakj9QTVpjLqX8=", + "requires": { + "esprima": "~1.0.4" + }, + "dependencies": { + "esprima": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.0.4.tgz", + "integrity": "sha1-n1V+CPw7TSbs6d00+Pv0drYlha0=" + } + } + }, "regenerate": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz", @@ -12105,6 +12287,14 @@ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=" }, + "resolve-protobuf-schema": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/resolve-protobuf-schema/-/resolve-protobuf-schema-2.1.0.tgz", + "integrity": "sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ==", + "requires": { + "protocol-buffers-schema": "^3.3.1" + } + }, "resolve-url": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", @@ -12268,6 +12458,11 @@ "aproba": "^1.1.1" } }, + "rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha1-P4Yt+pGrdmsUiF700BEkv9oHT7Q=" + }, "rxjs": { "version": "6.5.2", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.2.tgz", @@ -12606,6 +12801,23 @@ } } }, + "sharkdown": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/sharkdown/-/sharkdown-0.1.1.tgz", + "integrity": "sha512-exwooSpmo5s45lrexgz6Q0rFQM574wYIX3iDZ7RLLqOb7IAoQZu9nxlZODU972g19sR69OIpKP2cpHTzU+PHIg==", + "requires": { + "cardinal": "~0.4.2", + "minimist": "0.0.5", + "split": "~0.2.10" + }, + "dependencies": { + "minimist": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.5.tgz", + "integrity": "sha1-16oye87PUY+RBqxrjwA/o7zqhWY=" + } + } + }, "shebang-command": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", @@ -12962,6 +13174,14 @@ } } }, + "split": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/split/-/split-0.2.10.tgz", + "integrity": "sha1-Zwl8YB1pfOE2j0GPBs0gHPBSGlc=", + "requires": { + "through": "2" + } + }, "split-string": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", @@ -13239,6 +13459,14 @@ } } }, + "supercluster": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/supercluster/-/supercluster-6.0.2.tgz", + "integrity": "sha512-aa0v2HURjBTOpbcknilcfxGDuArM8khklKSmZ/T8ZXL0BuRwb5aRw95lz+2bmWpFvCXDX/+FzqHxmg0TIaJErw==", + "requires": { + "kdbush": "^3.0.0" + } + }, "supports-color": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", @@ -13523,6 +13751,11 @@ "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz", "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=" }, + "tinyqueue": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/tinyqueue/-/tinyqueue-2.0.3.tgz", + "integrity": "sha512-ppJZNDuKGgxzkHihX8v9v9G5f+18gzaTfrukGrq6ueg0lmH4nqVnA2IPG0AEH3jKEk2GRJCUhDoqpoiw3PHLBA==" + }, "tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", @@ -13988,6 +14221,16 @@ "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.0.tgz", "integrity": "sha512-iq+S7vZJE60yejDYM0ek6zg308+UZsdtPExWP9VZoCFCz1zkJoXFnAX7aZfd/ZwrkidzdUZL0C/ryW+JwAiIGw==" }, + "vt-pbf": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/vt-pbf/-/vt-pbf-3.1.1.tgz", + "integrity": "sha512-pHjWdrIoxurpmTcbfBWXaPwSmtPAHS105253P1qyEfSTV2HJddqjM+kIHquaT/L6lVJIk9ltTGc0IxR/G47hYA==", + "requires": { + "@mapbox/point-geometry": "0.1.0", + "@mapbox/vector-tile": "^1.3.1", + "pbf": "^3.0.5" + } + }, "w3c-hr-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.1.tgz", @@ -14307,6 +14550,11 @@ "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.3.tgz", "integrity": "sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg==" }, + "wgs84": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/wgs84/-/wgs84-0.0.0.tgz", + "integrity": "sha1-NP3FVZF7blfPKigu0ENxDASc3HY=" + }, "whatwg-encoding": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", diff --git a/package.json b/package.json index 9f58acf..9837e2b 100644 --- a/package.json +++ b/package.json @@ -4,9 +4,9 @@ "private": true, "dependencies": { "@types/lodash.sortby": "^4.7.6", - "leaflet": "^1.5.1", "lodash.groupby": "^4.6.0", "lodash.sortby": "^4.7.0", + "mapbox-gl": "^1.4.0", "moment": "^2.24.0", "node-sass": "^4.12.0", "react": "^16.9.0", diff --git a/src/App.js b/src/App.js index f2fb760..f5ac00a 100644 --- a/src/App.js +++ b/src/App.js @@ -5,32 +5,51 @@ import './App.scss'; function App() { //List of events - const [events, setEvents] = useState(null); + const [events, setEvents] = useState([]); //Current zip code search - input by user const [currZip, setCurrZip] = useState(null); - //Current event being hovered over - const [hoverEvent, setHoverEvent] = useState(null); - //Current selected location location filter - const [locFilt, setLocFilt] = useState(null); + //Current highlighted event (hovered in the list) + const [highlightedEvent, setHighlightedEvent] = useState({}); + //Used to filter by location, since there may be more than 1 event at a location. + //It's a string in the format lng+'&'+lat + const [locationFilter, setLocationFilter] = useState(null) + //Events that are within the map viewport. These should be shown in the list. + // This is a object keyed by eventid and used to filter the `events` object. + const [inViewEvents, setInViewEvents] = useState({}); - //Makes API call when zipcode entered + // Load all of the events useEffect(() => { - if(currZip != null){ - fetch("https://api.mobilize.us/v1/organizations/1316/events?timeslot_start=gte_now&zipcode=" + currZip) + fetch("https://warren-events.s3.amazonaws.com/data/events.json") .then((res)=>res.json()) - .then((data)=>setEvents(data['data'])); + .then((data)=>{ + setEvents(data) + }); + }, []); - //Reset states on new zipcode - setHoverEvent(null); - setLocFilt(null); + useEffect(() => { + // Use the mobilizemaerica api to look up zipcode to nearest event. + if(currZip == null) return; + fetch("https://api.mobilize.us/v1/organizations/1316/events?timeslot_start=gte_now&zipcode=" + currZip) + .then((res)=>res.json()) + .then(res => { + if (res.data && res.data.length > 0){ + let event = res.data[0] + + setHighlightedEvent({id: event.id, center:[event.location.location.longitude, event.location.location.latitude]}) + } + }); + + // Reset states on new zipcode + setInViewEvents({}); + setHighlightedEvent({}); + setLocationFilter(null); - } - }, [currZip]); + }, [currZip]) return (
- setCurrZip(newZip)} events={events} updatedHover={(newHover) => setHoverEvent(newHover)} locFilt={locFilt}/> - setLocFilt(newLoc)} locFilt={locFilt}/> + setCurrZip(newZip)} events={events} inViewEvents={inViewEvents} updatedHover={(newHover) => setHighlightedEvent(newHover)} locationFilter={locationFilter}/> + setLocationFilter(locKey)} highlightedEvent={highlightedEvent} inViewEvents={(keys) => setInViewEvents(keys)} locationFilter={locationFilter}/>
); } diff --git a/src/App.scss b/src/App.scss index 0e71153..101639c 100644 --- a/src/App.scss +++ b/src/App.scss @@ -61,7 +61,8 @@ html, body, #root, .app { .eventList{ background-color: white; - height: 500px; + max-height: 500px; + min-height: 250px; overflow:hidden; overflow-y:scroll; margin-bottom: 0px; @@ -76,10 +77,6 @@ html, body, #root, .app { text-transform: uppercase; } - :hover{ - color: white; - } - li{ list-style-type: none; padding-left: 10%; @@ -97,13 +94,24 @@ html, body, #root, .app { } - li:hover{ + li.event:hover{ background-color: #232444; + color: white; .eventRSVP{ visibility: visible; } } + li.highlighted{ + background-color: #232444; + color: white; + + .eventRSVP{ + visibility: visible; + } + } + + } diff --git a/src/EventList.js b/src/EventList.js index 2ccc0cd..822c54c 100644 --- a/src/EventList.js +++ b/src/EventList.js @@ -21,7 +21,7 @@ function EventTimes(props) { ) let sortedDates = Object.keys(sortedTimesByDate).sort(); - + const dateRowFactory = (date) => { let times = sortedTimesByDate[date]; let dayStr = times[0].start.format('ddd M/D') @@ -49,7 +49,29 @@ function EventTimes(props) { } export function EventList(props) { - const listEvents = props.events.map((event) => { + + var visableEvents = []; + if (props.locationFilter) { + visableEvents = props.events.filter(event => { + event.locationKey = event.location.location.longitude + '&' + event.location.location.latitude; + return (props.locationFilter === event.locationKey); + }) + } else { + // Filter based on the events that are currently in view. + var eventCount = 0; + visableEvents = props.events.filter(event => { + // limit to top matching events. to avoid list updating perf issues. + if (eventCount > 30) return false; + event.locationKey = event.location.location.longitude + '&' + event.location.location.latitude; + if (props.inViewEvents[event.locationKey]) { + eventCount +=1; + return true; + } + return false; + }) + } + + const listEvents = visableEvents.map((event, i) => { // Normalize Mobilize's time formatting into // easy-to-use moments @@ -62,26 +84,15 @@ export function EventList(props) { } }) - //Location filter - if(props.locFilt != null){ - if('location' in event && 'location' in event['location'] && 'latitude' in event['location']['location']){ - if(event['location']['location']['latitude'] !== props.locFilt['lat'] || event['location']['location']['longitude'] != props.locFilt['lng']){ - return(null); - } - } else { - return(null); - } - } - return ( - { props.updatedHover(event['currentTarget'].getAttribute('coord')) }} - onMouseLeave={(event) => { props.updatedHover(null) }}> -
  • + { props.updatedHover({locationKey: event['currentTarget'].getAttribute('eventlocation'), center:false}) }} + onMouseLeave={(event) => { props.updatedHover({}) }}> +
  • {event['title']}

    {event['location']['venue']} in {event['location']['locality']}

    @@ -90,10 +101,19 @@ export function EventList(props) {
  • - ) }); + listEvents.push((
  • +
    +

    + Don't see an event near you?
    + Join a virtual event or + host your own event! +

    +
    +
  • )) + return ( ); diff --git a/src/Map.js b/src/Map.js index 7d4e20a..5a3b02e 100644 --- a/src/Map.js +++ b/src/Map.js @@ -1,148 +1,168 @@ import React, { useState, useEffect, useRef } from 'react'; -import L from 'leaflet'; -import gMark from './img/w-marker-icon-2x.png'; -import hMark from './img/w-marker-icon-2x-highlighted.png'; -import sMark from './img/marker-shadow.png'; +import mapboxgl from 'mapbox-gl'; +require('mapbox-gl/dist/mapbox-gl.css'); export function Map(props){ - const [center, setCenter] = useState([39.8283, -98.5795]); const [locations, setLocations] = useState({}); - const [newCenter, setNewCenter] = useState(false); + const [mapReady, setMapReady] = useState(false); const map = useRef(); - const markers = useRef(); - - //Called to set/unset location filter - function locationFilter(event, set){ - console.log(event) - - if(set){ - props.selectLoc({ - 'lat': event['latlng']['lat'], - 'lng': event['latlng']['lng'] - }); - } else { - props.selectLoc(null); - } - } + const prevHighlightId = useRef() //First render useEffect(() => { // Create the map with US center - map.current = L.map('map', { - zoomControl: false - }).setView(center, (props.events != null) ? 8 : 4); - - //Initializes layergroup - markers.current = L.featureGroup().addTo(map.current); - markers.current.on("click", (event) => locationFilter(event, true)); - map.current.on("click", (event) => locationFilter(event, false)); - - - // Set up the OSM layer - L.tileLayer( - 'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { - attribution: 'Map data © OpenStreetMap contributors', - maxZoom: 18 - }).addTo(map.current); + mapboxgl.accessToken = 'pk.eyJ1IjoibWlja3QiLCJhIjoiY2sxam1xNmtsMHU5aTNob2N4YndlYXV0byJ9.LWG413QaYVY9bN4kAFu9eg'; + map.current = new mapboxgl.Map({ + container: 'map', + style: 'mapbox://styles/mickt/ck0rlk9834i721clibn70ajsa', + zoom: 3, + hash: true, + center: [-98.5795, 39.8283] + }); + map.current.on('load', _ => { + + map.current.addSource('locations', { + "type": "geojson", + "data": {type: 'FeatureCollection', features: []} + }); - L.control.zoom({ - position: 'topright' - }).addTo(map.current); - }, []); + map.current.addLayer({ + "id": "event-locations", + "source": "locations", + "type": "symbol", + "layout": { + "icon-allow-overlap": true, + "icon-anchor": "bottom", + "icon-size": 0.5, + "icon-image": "w-marker-icon" + }, + "paint": { + "icon-opacity": [ + "match", ["feature-state", "highlight"], + 1, 0, + 1 + ] + } + }); + map.current.addLayer({ + "id": "event-locations-highlight", + "source": "locations", + "type": "symbol", + "layout": { + "icon-allow-overlap": true, + "icon-image": "w-marker-icon-highlighted", + "icon-anchor": "bottom", + "icon-size": 0.6 + }, + "paint": { + "icon-opacity": [ + "match", ["feature-state", "highlight"], + 1, 1, + 0 + ] + } + }); - //When locations are updated, generate new markers - useEffect(() => { + // Center the map on the coordinates of any clicked symbol from the 'symbols' layer. + map.current.on('click', function (e) { + props.setLocationFilter(null); + }); - if(Object.keys(locations).length > 0){ - markers.current.clearLayers(); + map.current.on('click', 'event-locations', function (e) { + props.setLocationFilter(e.features[0].properties.locationKey); + if (map.current.getZoom() < 8) { + map.current.jumpTo({center: e.features[0].geometry.coordinates, zoom: 10}); + } else { + map.current.jumpTo({center: e.features[0].geometry.coordinates}); + } - if(newCenter){ - map.current.setView(center, 8); - setNewCenter(false); - } + }); - var generalIcon = new L.Icon({ - iconUrl: gMark, - shadowUrl: sMark, - iconSize: [25, 41], - iconAnchor: [12, 41], - popupAnchor: [1, -34], - shadowSize: [41, 41], + // Change the cursor to a pointer when the it enters a feature in the 'symbols' layer. + map.current.on('mouseenter', 'event-locations', function () { + map.current.getCanvas().style.cursor = 'pointer'; }); - var highlightedIcon = new L.Icon({ - iconUrl: hMark, - shadowUrl: sMark, - iconSize: [25, 41], - iconAnchor: [12, 41], - popupAnchor: [1, -34], - shadowSize: [41, 41], + + // Change it back to a pointer when it leaves. + map.current.on('mouseleave', 'event-locations', function () { + map.current.getCanvas().style.cursor = ''; }); - for (var key in locations) { - let highlighted = false; + function inViewFeatures(e) { + var features = map.current.queryRenderedFeatures({ layers: ['event-locations', 'event-locations-highlight'] }); + var inView = {}; + features.forEach(f => { + inView[f.properties.locationKey] = true; + }); + props.inViewEvents(inView); + } - if(key === props.hoverMarker || (props.locFilt !== null && key === props.locFilt['lat'] + "&" + props.locFilt['lng'])){ - console.log("matching"); - highlighted = true; - } + // once the map settles in a location, then reset the features in view + map.current.on('idle', inViewFeatures); - let cord = key.split("&"); + setMapReady(true); + }) + }, []); - if(highlighted){ - L.marker([parseFloat(cord[0]), parseFloat(cord[1])], {icon: highlightedIcon, zIndexOffset: 1000}).addTo(markers.current); - } else { - L.marker([parseFloat(cord[0]), parseFloat(cord[1])], {icon: generalIcon}).addTo(markers.current); - } + function highlight(currentId, center) { + if (prevHighlightId.current) map.current.setFeatureState({source: 'locations', id: prevHighlightId.current}, { highlight: 0}); + if (currentId) map.current.setFeatureState({source: 'locations', id: currentId}, { highlight: 1}); - } - } - }, [locations, props.hoverMarker, props.locFilt]); + prevHighlightId.current = currentId; + if (center) + map.current.jumpTo({center: center, zoom: 10}); + } - //Iterates through new events useEffect(() => { + if (mapReady === false) return; + // if list is fitlered to a location that marker is highlighted, + // otherwise if an event is hovered in the list. + var locKey = props.locationFilter || props.highlightedEvent.locationKey; + var id = (locations[locKey] && locations[locKey].id) || null; + highlight(id, props.highlightedEvent.center); + }, [ props.highlightedEvent, props.locationFilter, mapReady]) - if(props.events != null){ - - //Initiates map's focus at the first event (typically the closest to the provided zipcode) with a valid lat & long position - let first = 0; - if (!('location' in props.events[first]) || !('location' in props.events[first]['location']) || !('latitude' in props.events[first]['location']['location'])) { - first++; - } - var lat = props.events[first]['location']['location']['latitude']; - var long = props.events[first]['location']['location']['longitude']; - - if(center[0] !== lat || center[0] !== long){ - setCenter([lat, long]); - setNewCenter(true); - } - - var places = {}; - - props.events.forEach(function(event, index) { - - //If has longitude and latitute - if ('location' in event && 'location' in event['location'] && 'latitude' in event['location']['location']) { - - //Creates string key for {places} dictionary - let str = event['location']['location']['latitude'] + "&" + event['location']['location']['longitude']; - //Creates or adds to a location - adds HTML code for event list for that location - if (str in places) { - places[str] = places[str] + 1; - } else { - places[str] = 1; - } + useEffect(() => { + if (mapReady === false) return; - } - }); - setLocations(places); - } + var geojson = {type: 'FeatureCollection', features: Object.values(locations)}; + map.current.getSource('locations').setData(geojson); + }, [locations, mapReady]) + //Iterates through new events + useEffect(() => { + if (props.events === null) return; + + //deduped locations, so we dont need to render multiple pins for the same location. + var locations = {}; + + props.events.forEach((e, i) => { + + var locationKey = e.location.location.longitude + '&' + e.location.location.latitude; + + locations[locationKey] = { + type: 'Feature', + id: i+1, // id based on iterator used for feature state lookups to highlight markers. + // 0 id doesnt work (bug) + properties:{ + locationKey: locationKey + }, + geometry: { + type: 'Point', + coordinates: [ + parseFloat(e.location.location.longitude), + parseFloat(e.location.location.latitude) + ] + } + }; + }); + setLocations(locations); }, [props.events]); diff --git a/src/SearchBar.js b/src/SearchBar.js index dafd20f..a5aea5d 100644 --- a/src/SearchBar.js +++ b/src/SearchBar.js @@ -8,7 +8,6 @@ export function SearchBar(props){ function onlySetNumbers(event){ let baseValue = event.target.value; let replacedVal = baseValue.replace(/\D*/g, '') - console.log(`baseValue: ${baseValue}, replacedVal: ${replacedVal}`); setInput(replacedVal) } @@ -17,14 +16,17 @@ export function SearchBar(props){ props.updateZip(input) } + var eventlist = []; + if (props.locFilt !== null || props.nearby !== null) { + eventlist = ( props.updatedHover(item)}/>) + } + return( -
    +
    - {props.events !== null && - props.updatedHover(item)}/> - } + { eventlist }
    ); }