From aa7c929e86ff97affdfa103adf23fb23faaf1e69 Mon Sep 17 00:00:00 2001 From: Jason Lampel Date: Sat, 29 Aug 2020 15:26:45 -0600 Subject: [PATCH 01/18] Geocoder: init (great start, no flyTo offset yet) --- package.json | 1 + src/components/map/Map.tsx | 27 +- src/components/map/MapCtrlBtns.tsx | 119 +++++-- src/components/map/react-geocoder.d.ts | 1 + src/components/map/types.ts | 2 +- src/components/nav/TopBar.tsx | 24 +- yarn.lock | 430 ++++++++++++++++++++++++- 7 files changed, 543 insertions(+), 61 deletions(-) create mode 100644 src/components/map/react-geocoder.d.ts diff --git a/package.json b/package.json index 1b21b6de..4d3adaf5 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "react-dom": "^16.13.1", "react-icons": "^3.10.0", "react-map-gl": "^5.2.7", + "react-map-gl-geocoder": "^2.0.16", "react-query": "^2.12.1", "react-router-dom": "^5.2.0", "react-scripts": "3.4.1", diff --git a/src/components/map/Map.tsx b/src/components/map/Map.tsx index ed0a97a9..f3111fb1 100644 --- a/src/components/map/Map.tsx +++ b/src/components/map/Map.tsx @@ -2,8 +2,7 @@ import React, { FC, useState, useContext, useEffect } from 'react' import { useHistory } from 'react-router-dom' import { AttributionControl, Map as MbMap, setRTLTextPlugin } from 'mapbox-gl' import MapGL, { InteractiveMap, MapLoadEvent } from 'react-map-gl' -import { Theme } from '@material-ui/core/styles' -import useMediaQuery from '@material-ui/core/useMediaQuery' +import { useTheme } from '@material-ui/core/styles' import 'mapbox-gl/dist/mapbox-gl.css' @@ -19,7 +18,11 @@ import * as MapTypes from './types' import * as mapUtils from './utils' import * as mapConfig from './config' import { LangRecordSchema } from '../../context/types' -import { getIDfromURLparams, findFeatureByID } from '../../utils' +import { + getIDfromURLparams, + findFeatureByID, + useWindowResize, +} from '../../utils' const { layerId: sourceLayer, internalSrcID } = mapConfig.mbStyleTileConfig @@ -33,7 +36,7 @@ if (typeof window !== undefined && typeof setRTLTextPlugin === 'function') { // Yeah not today TS, thanks anyway: // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - null, + null, // supposed to be an error-handling function true // lazy: only load when the map first encounters Hebrew or Arabic text ) } @@ -48,23 +51,26 @@ export const Map: FC = ({ const { state, dispatch } = useContext(GlobalContext) const mapRef: React.RefObject = React.createRef() const { selFeatAttribs, mapLoaded, langFeatIDs } = state - const isDesktop = useMediaQuery((theme: Theme) => theme.breakpoints.up('md')) - + const theme = useTheme() + const [isDesktop, setIsDesktop] = useState(false) + const { width, height } = useWindowResize() const [viewport, setViewport] = useState(mapConfig.initialMapState) const [mapOffset, setMapOffset] = useState<[number, number]>([0, 0]) const [popupOpen, setPopupOpen] = useState(null) const [tooltipOpen, setTooltipOpen] = useState( null ) - /* eslint-disable react-hooks/exhaustive-deps */ // ^^^^^ otherwise it wants things like mapRef and dispatch 24/7 // Set the offset for transitions like `flyTo` and `easeTo` useEffect((): void => { - const offset = mapUtils.prepMapOffset(isDesktop) + const deskBreakPoint = theme.breakpoints.values.md + const wideFella = width >= deskBreakPoint + const offset = mapUtils.prepMapOffset(wideFella) + setIsDesktop(wideFella) setMapOffset(offset) - }, [isDesktop]) + }, [width, height]) // TODO: another file useEffect((): void => { @@ -105,6 +111,7 @@ export const Map: FC = ({ }) }, [langFeatIDs]) + // TODO: put in... legend? useEffect((): void => { initLegend(dispatch, state.activeLangSymbGroupId, symbLayers) }, [state.activeLangSymbGroupId]) @@ -385,7 +392,9 @@ export const Map: FC = ({ )} { onMapCtrlClick(actionID) }} diff --git a/src/components/map/MapCtrlBtns.tsx b/src/components/map/MapCtrlBtns.tsx index d5502258..70944427 100644 --- a/src/components/map/MapCtrlBtns.tsx +++ b/src/components/map/MapCtrlBtns.tsx @@ -1,5 +1,7 @@ -import React, { FC } from 'react' +import React, { FC, useCallback } from 'react' import { makeStyles, createStyles, Theme } from '@material-ui/core/styles' +import { InteractiveMap, ViewportProps } from 'react-map-gl' +import Geocoder from 'react-map-gl-geocoder' import { SpeedDial, SpeedDialIcon, @@ -7,20 +9,27 @@ import { CloseReason, } from '@material-ui/lab' import { MdMoreVert, MdClose } from 'react-icons/md' +import { BiMapPin } from 'react-icons/bi' import { FiHome, FiZoomIn, FiZoomOut, FiInfo } from 'react-icons/fi' +import 'react-map-gl-geocoder/dist/mapbox-gl-geocoder.css' + import { MapControlAction } from './types' +import { MAPBOX_TOKEN } from './config' -type MapCtrlBtnsComponent = { +type MapCtrlBtnsProps = { // Render prop so we don't have pass a million props to this component onMapCtrlClick: (actionID: MapControlAction) => void isDesktop: boolean + setViewport: React.Dispatch + mapRef: React.RefObject } type CtrlBtnConfig = { id: MapControlAction icon: React.ReactNode name: string + customFn?: boolean } const useStyles = makeStyles((theme: Theme) => @@ -54,14 +63,22 @@ const ctrlBtnsConfig = [ { id: 'in', icon: , name: 'Zoom in' }, { id: 'out', icon: , name: 'Zoom out' }, { id: 'home', icon: , name: 'Zoom home' }, + { + id: 'loc-search', + icon: , + name: 'Search by location', + customFn: true, + }, { id: 'info', icon: , name: 'About & Info' }, ] as CtrlBtnConfig[] -export const MapCtrlBtns: FC = (props) => { - const { isDesktop, onMapCtrlClick } = props +export const MapCtrlBtns: FC = (props) => { + const { isDesktop, onMapCtrlClick, mapRef, setViewport } = props const classes = useStyles() - const [open, setOpen] = React.useState(true) + const [speedDialOpen, setSpeedDialOpen] = React.useState(true) + const [locSearchOpen, setLocSearchOpen] = React.useState(false) const size = isDesktop ? 'medium' : 'small' + const geocoderContainerRef = React.useRef(null) const handleClose = ( e: React.SyntheticEvent, Event>, @@ -74,37 +91,79 @@ export const MapCtrlBtns: FC = (props) => { return } - setOpen(false) + setSpeedDialOpen(false) } const handleRootClick = () => { - setOpen(!open) + setSpeedDialOpen(!speedDialOpen) } + const handleGeocoderViewportChange = useCallback((newViewport) => { + const geocoderDefaultOverrides = { transitionDuration: 1000 } + + return handleViewportChange({ + ...newViewport, + ...geocoderDefaultOverrides, + }) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) + + const handleViewportChange = useCallback( + (newViewport) => setViewport(newViewport), + // eslint-disable-next-line react-hooks/exhaustive-deps + [] + ) + return ( - } icon={} />} - onClose={handleClose} - open={open} - direction="down" - FabProps={{ size }} - onClick={handleRootClick} - > - {ctrlBtnsConfig.map((action) => ( - { - e.stopPropagation() // prevent closing the menu - onMapCtrlClick(action.id) - }} - FabProps={{ size }} // TODO: uhhhh breakpoints? Why is this needed? + <> +
+ - ))} - +
+ } icon={} />} + onClose={handleClose} + open={speedDialOpen} + direction="down" + FabProps={{ size }} + onClick={handleRootClick} + > + {ctrlBtnsConfig.map((action) => ( + { + e.stopPropagation() // prevent closing the menu + + // GROSS + if (!action.customFn) { + onMapCtrlClick(action.id) + } else { + setLocSearchOpen(!locSearchOpen) + } + }} + FabProps={{ size }} // TODO: uhhhh breakpoints? Why is this needed? + /> + ))} + + ) } diff --git a/src/components/map/react-geocoder.d.ts b/src/components/map/react-geocoder.d.ts new file mode 100644 index 00000000..932527c2 --- /dev/null +++ b/src/components/map/react-geocoder.d.ts @@ -0,0 +1 @@ +declare module 'react-map-gl-geocoder' diff --git a/src/components/map/types.ts b/src/components/map/types.ts index 0c8918f6..141597f6 100644 --- a/src/components/map/types.ts +++ b/src/components/map/types.ts @@ -101,7 +101,7 @@ export type MapComponent = { export type LangIconConfig = { icon: string; id: string } -export type MapControlAction = 'home' | 'in' | 'out' | 'info' +export type MapControlAction = 'home' | 'in' | 'out' | 'info' | 'loc-search' export type FlyToCoords = ( map: Map, diff --git a/src/components/nav/TopBar.tsx b/src/components/nav/TopBar.tsx index 5d51242b..b6605f69 100644 --- a/src/components/nav/TopBar.tsx +++ b/src/components/nav/TopBar.tsx @@ -17,19 +17,17 @@ export const TopBar: FC = () => { topBarRoot, } = classes + // Need the `id` in order to find unique element for `map.setPadding` return ( - <> - {/* Need the `id` in order to find unique element for `map.setPadding` */} -
-
- - - Languages - of New York City - - -
-
- +
+
+ + + Languages + of New York City + + +
+
) } diff --git a/yarn.lock b/yarn.lock index f0c72dfb..6532feac 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1395,6 +1395,14 @@ "@types/yargs" "^15.0.0" chalk "^4.0.0" +"@mapbox/fusspot@^0.4.0": + version "0.4.0" + resolved "https://registry.yarnpkg.com/@mapbox/fusspot/-/fusspot-0.4.0.tgz#5ac625b5ed31695fda465fb0413ee0379bdf553c" + integrity sha512-6sys1vUlhNCqMvJOqPEPSi0jc9tg7aJ//oG1A16H3PXoIt9whtNngD7UzBHUVTH15zunR/vRvMtGNVsogm1KzA== + dependencies: + is-plain-obj "^1.1.0" + xtend "^4.0.1" + "@mapbox/geojson-rewind@^0.5.0": version "0.5.0" resolved "https://registry.yarnpkg.com/@mapbox/geojson-rewind/-/geojson-rewind-0.5.0.tgz#91f0ad56008c120caa19414b644d741249f4f560" @@ -1413,16 +1421,56 @@ resolved "https://registry.yarnpkg.com/@mapbox/jsonlint-lines-primitives/-/jsonlint-lines-primitives-2.0.2.tgz#ce56e539f83552b58d10d672ea4d6fc9adc7b234" integrity sha1-zlblOfg1UrWNENZy6k1vya3HsjQ= +"@mapbox/mapbox-gl-geocoder@4.7.0": + version "4.7.0" + resolved "https://registry.yarnpkg.com/@mapbox/mapbox-gl-geocoder/-/mapbox-gl-geocoder-4.7.0.tgz#ff3b3b6a8e89e4c62df46be8e95502b6c5e510bc" + integrity sha512-Fy4jlWWCQt2jKsQ7Agt2nDF1bEY9easOBJVE+yXdpC2D/y/FPnXEQkj9ryhZL05j1iQVOIyU87pM3GI3ETmOVA== + dependencies: + "@mapbox/mapbox-sdk" "^0.11.0" + lodash.debounce "^4.0.6" + nanoid "^2.0.1" + subtag "^0.5.0" + suggestions "^1.6.0" + xtend "^4.0.1" + "@mapbox/mapbox-gl-supported@^1.5.0": version "1.5.0" resolved "https://registry.yarnpkg.com/@mapbox/mapbox-gl-supported/-/mapbox-gl-supported-1.5.0.tgz#f60b6a55a5d8e5ee908347d2ce4250b15103dc8e" integrity sha512-/PT1P6DNf7vjEEiPkVIRJkvibbqWtqnyGaBz3nfRdcxclNSnSdaLU5tfAgcD7I8Yt5i+L19s406YLl1koLnLbg== +"@mapbox/mapbox-sdk@^0.11.0": + version "0.11.0" + resolved "https://registry.yarnpkg.com/@mapbox/mapbox-sdk/-/mapbox-sdk-0.11.0.tgz#7aabc10f72480520b8f26e179a69dbab2f9342e5" + integrity sha512-NNFctJEq2JmcIXBRoQe5QZi2XbxgFFKxnbrD4M7Mb/HRFMetb6jq7+eAwCzgb0UVQM+nXP7YCO43hINq/SuT6g== + dependencies: + "@mapbox/fusspot" "^0.4.0" + "@mapbox/parse-mapbox-token" "^0.2.0" + "@mapbox/polyline" "^1.0.0" + eventemitter3 "^3.1.0" + form-data "^3.0.0" + got "^8.3.2" + is-plain-obj "^1.1.0" + xtend "^4.0.1" + +"@mapbox/parse-mapbox-token@^0.2.0": + version "0.2.0" + resolved "https://registry.yarnpkg.com/@mapbox/parse-mapbox-token/-/parse-mapbox-token-0.2.0.tgz#34049d948868376f689189a5ea0e3cd2d9284b91" + integrity sha512-BjeuG4sodYaoTygwXIuAWlZV6zUv4ZriYAQhXikzx+7DChycMUQ9g85E79Htat+AsBg+nStFALehlOhClYm5cQ== + dependencies: + base-64 "^0.1.0" + "@mapbox/point-geometry@0.1.0", "@mapbox/point-geometry@^0.1.0", "@mapbox/point-geometry@~0.1.0": version "0.1.0" resolved "https://registry.yarnpkg.com/@mapbox/point-geometry/-/point-geometry-0.1.0.tgz#8a83f9335c7860effa2eeeca254332aa0aeed8f2" integrity sha1-ioP5M1x4YO/6Lu7KJUMyqgru2PI= +"@mapbox/polyline@^1.0.0": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@mapbox/polyline/-/polyline-1.1.1.tgz#ab96e5e6936f4847a4894e14558daf43e40e3bd2" + integrity sha512-z9Sl7NYzsEIrAza658H92mc0OvpBjQwjp7Snv4xREKhsCMat7m1IKdWJMjQ5opiPYa0veMf7kCaSd1yx55AhmQ== + dependencies: + meow "^6.1.1" + "@mapbox/tiny-sdf@^1.1.1": version "1.1.1" resolved "https://registry.yarnpkg.com/@mapbox/tiny-sdf/-/tiny-sdf-1.1.1.tgz#16a20c470741bfe9191deb336f46e194da4a91ff" @@ -1553,6 +1601,11 @@ resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz#2b5a3ab3f918cca48a8c754c08168e3f03eba61b" integrity sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw== +"@sindresorhus/is@^0.7.0": + version "0.7.0" + resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.7.0.tgz#9a06f4f137ee84d7df0460c1fdb1135ffa6c50fd" + integrity sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow== + "@sinonjs/commons@^1.7.0": version "1.8.1" resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.1.tgz#e7df00f98a203324f6dc7cc606cad9d4a8ab2217" @@ -1836,11 +1889,21 @@ resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== +"@types/minimist@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.0.tgz#69a23a3ad29caf0097f06eda59b361ee2f0639f6" + integrity sha1-aaI6OtKcrwCX8G7aWbNh7i8GOfY= + "@types/node@*", "@types/node@^14.0.13": version "14.6.2" resolved "https://registry.yarnpkg.com/@types/node/-/node-14.6.2.tgz#264b44c5a28dfa80198fc2f7b6d3c8a054b9491f" integrity sha512-onlIwbaeqvZyniGPfdw/TEhKIh79pz66L1q06WUQqJLnAb6wbjvOtepLYTGHTqzdXgBYIE3ZdmqHDGsRsbBz7A== +"@types/normalize-package-data@^2.4.0": + version "2.4.0" + resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz#e486d0d97396d79beedd0a6e33f4534ff6b4973e" + integrity sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA== + "@types/parse-json@^4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" @@ -2844,6 +2907,11 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= +base-64@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/base-64/-/base-64-0.1.0.tgz#780a99c84e7d600260361511c4877613bf24f6bb" + integrity sha1-eAqZyE59YAJgNhURxId2E78k9rs= + base64-arraybuffer@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.2.0.tgz#4b944fac0191aa5907afe2d8c999ccc57ce80f45" @@ -3194,6 +3262,19 @@ cache-base@^1.0.1: union-value "^1.0.0" unset-value "^1.0.0" +cacheable-request@^2.1.1: + version "2.1.4" + resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-2.1.4.tgz#0d808801b6342ad33c91df9d0b44dc09b91e5c3d" + integrity sha1-DYCIAbY0KtM8kd+dC0TcCbkeXD0= + dependencies: + clone-response "1.0.2" + get-stream "3.0.0" + http-cache-semantics "3.8.1" + keyv "3.0.0" + lowercase-keys "1.0.0" + normalize-url "2.0.1" + responselike "1.0.2" + call-me-maybe@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/call-me-maybe/-/call-me-maybe-1.0.1.tgz#26d208ea89e37b5cbde60250a15f031c16a4d66b" @@ -3231,7 +3312,7 @@ camel-case@^4.1.1: pascal-case "^3.1.1" tslib "^1.10.0" -camelcase-keys@^6.0.0: +camelcase-keys@^6.0.0, camelcase-keys@^6.2.2: version "6.2.2" resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-6.2.2.tgz#5e755d6ba51aa223ec7d3d52f25778210f9dc3c0" integrity sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg== @@ -3491,6 +3572,13 @@ clone-deep@^4.0.1: kind-of "^6.0.2" shallow-clone "^3.0.0" +clone-response@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b" + integrity sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws= + dependencies: + mimic-response "^1.0.0" + clsx@^1.0.2, clsx@^1.0.4: version "1.1.1" resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.1.1.tgz#98b3134f9abbdf23b2663491ace13c5c03a73188" @@ -3573,7 +3661,7 @@ colorette@^1.2.1: resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.1.tgz#4d0b921325c14faf92633086a536db6e89564b1b" integrity sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw== -combined-stream@^1.0.6, combined-stream@~1.0.6: +combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== @@ -4238,7 +4326,15 @@ debug@^4.0.1, debug@^4.1.0, debug@^4.1.1: dependencies: ms "^2.1.1" -decamelize@^1.2.0: +decamelize-keys@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/decamelize-keys/-/decamelize-keys-1.1.0.tgz#d171a87933252807eb3cb61dc1c1445d078df2d9" + integrity sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk= + dependencies: + decamelize "^1.1.0" + map-obj "^1.0.0" + +decamelize@^1.1.0, decamelize@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= @@ -4253,6 +4349,13 @@ decode-uri-component@^0.2.0: resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= +decompress-response@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3" + integrity sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M= + dependencies: + mimic-response "^1.0.0" + dedent@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" @@ -4550,6 +4653,11 @@ dotenv@8.2.0: resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.2.0.tgz#97e619259ada750eea3e4ea3e26bceea5424b16a" integrity sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw== +duplexer3@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" + integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI= + duplexer@^0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6" @@ -5206,6 +5314,11 @@ etag@~1.8.1: resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= +eventemitter3@^3.1.0: + version "3.1.2" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.2.tgz#2d3d48f9c346698fce83a85d7d664e98535df6e7" + integrity sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q== + eventemitter3@^4.0.0: version "4.0.7" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" @@ -5659,6 +5772,15 @@ fork-ts-checker-webpack-plugin@3.1.1: tapable "^1.0.0" worker-rpc "^0.1.0" +form-data@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.0.tgz#31b7e39c85f1355b7139ee0c647cf0de7f83c682" + integrity sha512-CKMFDglpbMi6PyN+brwB9Q/GOw0eAnsrEZDgcsH5Krhz5Od/haKHAX0NmQfha2zPPz0JpWzA7GJHGSnvCRLWsg== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + form-data@~2.3.2: version "2.3.3" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" @@ -5685,7 +5807,7 @@ fresh@0.5.2: resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= -from2@^2.1.0: +from2@^2.1.0, from2@^2.1.1: version "2.3.0" resolved "https://registry.yarnpkg.com/from2/-/from2-2.3.0.tgz#8bfb5502bde4a4d36cfdeea007fcca21d7e382af" integrity sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8= @@ -5770,6 +5892,11 @@ functional-red-black-tree@^1.0.1: resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= +fuzzy@^0.1.1: + version "0.1.3" + resolved "https://registry.yarnpkg.com/fuzzy/-/fuzzy-0.1.3.tgz#4c76ec2ff0ac1a36a9dccf9a00df8623078d4ed8" + integrity sha1-THbsL/CsGjap3M+aAN+GIweNTtg= + gensync@^1.0.0-beta.1: version "1.0.0-beta.1" resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.1.tgz#58f4361ff987e5ff6e1e7a210827aa371eaac269" @@ -5805,6 +5932,11 @@ get-stdin@^7.0.0: resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-7.0.0.tgz#8d5de98f15171a125c5e516643c7a6d0ea8a96f6" integrity sha512-zRKcywvrXlXsA0v0i9Io4KDRaAw7+a1ZpjRwl9Wox8PFlVCCHra7E9c4kqXCoCM9nR5tBkaTTZRBoCm60bFqTQ== +get-stream@3.0.0, get-stream@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" + integrity sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ= + get-stream@^4.0.0: version "4.1.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" @@ -5920,6 +6052,29 @@ globby@^6.1.0: pify "^2.0.0" pinkie-promise "^2.0.0" +got@^8.3.2: + version "8.3.2" + resolved "https://registry.yarnpkg.com/got/-/got-8.3.2.tgz#1d23f64390e97f776cac52e5b936e5f514d2e937" + integrity sha512-qjUJ5U/hawxosMryILofZCkm3C84PLJS/0grRIpjAwu+Lkxxj5cxeCU25BG0/3mDSpXKTyZr8oh8wIgLaH0QCw== + dependencies: + "@sindresorhus/is" "^0.7.0" + cacheable-request "^2.1.1" + decompress-response "^3.3.0" + duplexer3 "^0.1.4" + get-stream "^3.0.0" + into-stream "^3.1.0" + is-retry-allowed "^1.1.0" + isurl "^1.0.0-alpha5" + lowercase-keys "^1.0.0" + mimic-response "^1.0.0" + p-cancelable "^0.4.0" + p-timeout "^2.0.1" + pify "^3.0.0" + safe-buffer "^5.1.1" + timed-out "^4.0.1" + url-parse-lax "^3.0.0" + url-to-options "^1.0.1" + graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.2, graceful-fs@^4.2.4: version "4.2.4" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" @@ -5966,6 +6121,11 @@ har-validator@~5.1.3: ajv "^6.12.3" har-schema "^2.0.0" +hard-rejection@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/hard-rejection/-/hard-rejection-2.1.0.tgz#1c6eda5c1685c63942766d79bb40ae773cecd883" + integrity sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA== + harmony-reflect@^1.4.6: version "1.6.1" resolved "https://registry.yarnpkg.com/harmony-reflect/-/harmony-reflect-1.6.1.tgz#c108d4f2bb451efef7a37861fdbdae72c9bdefa9" @@ -5988,11 +6148,23 @@ has-flag@^4.0.0: resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== +has-symbol-support-x@^1.4.1: + version "1.4.2" + resolved "https://registry.yarnpkg.com/has-symbol-support-x/-/has-symbol-support-x-1.4.2.tgz#1409f98bc00247da45da67cee0a36f282ff26455" + integrity sha512-3ToOva++HaW+eCpgqZrCfN51IPB+7bJNVT6CUATzueB5Heb8o6Nam0V3HG5dlDvZU1Gn5QLcbahiKw/XVk5JJw== + has-symbols@^1.0.0, has-symbols@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8" integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg== +has-to-string-tag-x@^1.2.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/has-to-string-tag-x/-/has-to-string-tag-x-1.4.1.tgz#a045ab383d7b4b2012a00148ab0aa5f290044d4d" + integrity sha512-vdbKfmw+3LoOYVr+mtxHaX5a96+0f3DljYd8JOqvOLsf5mw2Otda2qCDT9qRqLAhrjyQ0h7ual5nOiASpsGNFw== + dependencies: + has-symbol-support-x "^1.4.1" + has-value@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" @@ -6184,6 +6356,11 @@ htmlparser2@^3.3.0: inherits "^2.0.1" readable-stream "^3.1.1" +http-cache-semantics@3.8.1: + version "3.8.1" + resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz#39b0e16add9b605bf0a9ef3d9daaf4843b4cacd2" + integrity sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w== + http-deceiver@^1.2.7: version "1.2.7" resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87" @@ -6496,6 +6673,14 @@ internal-slot@^1.0.2: has "^1.0.3" side-channel "^1.0.2" +into-stream@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/into-stream/-/into-stream-3.1.0.tgz#96fb0a936c12babd6ff1752a17d05616abd094c6" + integrity sha1-lvsKk2wSur1v8XUqF9BWFqvQlMY= + dependencies: + from2 "^2.1.1" + p-is-promise "^1.1.0" + invariant@^2.2.2, invariant@^2.2.4: version "2.2.4" resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" @@ -6737,6 +6922,11 @@ is-obj@^2.0.0: resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== +is-object@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-object/-/is-object-1.0.1.tgz#8952688c5ec2ffd6b03ecc85e769e02903083470" + integrity sha1-iVJojF7C/9awPsyF52ngKQMINHA= + is-path-cwd@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-2.2.0.tgz#67d43b82664a7b5191fd9119127eb300048a9fdb" @@ -6756,7 +6946,7 @@ is-path-inside@^2.1.0: dependencies: path-is-inside "^1.0.2" -is-plain-obj@^1.0.0: +is-plain-obj@^1.0.0, is-plain-obj@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" integrity sha1-caUMhCnfync8kqOQpKA7OfzVHT4= @@ -6790,6 +6980,11 @@ is-resolvable@^1.0.0: resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.1.0.tgz#fb18f87ce1feb925169c9a407c19318a3206ed88" integrity sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg== +is-retry-allowed@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz#d778488bd0a4666a3be8a1482b9f2baafedea8b4" + integrity sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg== + is-root@2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-root/-/is-root-2.1.0.tgz#809e18129cf1129644302a4f8544035d51984a9c" @@ -6923,6 +7118,14 @@ istanbul-reports@^2.2.6: dependencies: html-escaper "^2.0.0" +isurl@^1.0.0-alpha5: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isurl/-/isurl-1.0.0.tgz#b27f4f49f3cdaa3ea44a0a5b7f3462e6edc39d67" + integrity sha512-1P/yWsxPlDtn7QeRD+ULKQPaIaN6yF368GZ2vDfv0AL0NwpStafjWCDDdn0k8wgFMWpVAqG7oJhxHnlud42i9w== + dependencies: + has-to-string-tag-x "^1.2.0" + is-object "^1.0.1" + jest-canvas-mock@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/jest-canvas-mock/-/jest-canvas-mock-2.2.0.tgz#45fbc58589c6ce9df50dc90bd8adce747cbdada7" @@ -7504,6 +7707,11 @@ jsesc@~0.5.0: resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" integrity sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0= +json-buffer@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898" + integrity sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg= + json-parse-better-errors@^1.0.1, json-parse-better-errors@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" @@ -7690,6 +7898,13 @@ kdbush@^3.0.0: resolved "https://registry.yarnpkg.com/kdbush/-/kdbush-3.0.0.tgz#f8484794d47004cc2d85ed3a79353dbe0abc2bf0" integrity sha512-hRkd6/XW4HTsA9vjVpY9tuXJYLSlelnkTmVFu4M9/7MIYQtFcHpbugAU7UbOfjOiVSVYl2fqgBuJ32JUmRo5Ew== +keyv@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.0.0.tgz#44923ba39e68b12a7cec7df6c3268c031f2ef373" + integrity sha512-eguHnq22OE3uVoSYG0LVWNP+4ppamWr9+zWBe1bsNcovIMy6huUJFPgy4mGwCd/rnl3vOLGW1MTlu4c57CT1xA== + dependencies: + json-buffer "3.0.0" + killable@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/killable/-/killable-1.0.1.tgz#4c8ce441187a061c7474fb87ca08e2a638194892" @@ -7721,7 +7936,7 @@ kind-of@^5.0.0: resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d" integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw== -kind-of@^6.0.0, kind-of@^6.0.2: +kind-of@^6.0.0, kind-of@^6.0.2, kind-of@^6.0.3: version "6.0.3" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== @@ -7921,6 +8136,11 @@ lodash._reinterpolate@^3.0.0: resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d" integrity sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0= +lodash.debounce@^4.0.6: + version "4.0.8" + resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" + integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168= + lodash.memoize@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" @@ -8017,6 +8237,16 @@ lower-case@^2.0.1: dependencies: tslib "^1.10.0" +lowercase-keys@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.0.tgz#4e3366b39e7f5457e35f1324bdf6f88d0bfc7306" + integrity sha1-TjNms55/VFfjXxMkvfb4jQv8cwY= + +lowercase-keys@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" + integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA== + lru-cache@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" @@ -8070,6 +8300,11 @@ map-cache@^0.2.2: resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" integrity sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8= +map-obj@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d" + integrity sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0= + map-obj@^4.0.0: version "4.1.0" resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-4.1.0.tgz#b91221b542734b9f14256c0132c897c5d7256fd5" @@ -8191,6 +8426,23 @@ memory-fs@^0.5.0: errno "^0.1.3" readable-stream "^2.0.1" +meow@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/meow/-/meow-6.1.1.tgz#1ad64c4b76b2a24dfb2f635fddcadf320d251467" + integrity sha512-3YffViIt2QWgTy6Pale5QpopX/IvU3LPL03jOTqp6pGj3VjesdO/U8CuHMKpnQr4shCNCM5fd5XFFvIIl6JBHg== + dependencies: + "@types/minimist" "^1.2.0" + camelcase-keys "^6.2.2" + decamelize-keys "^1.1.0" + hard-rejection "^2.1.0" + minimist-options "^4.0.2" + normalize-package-data "^2.5.0" + read-pkg-up "^7.0.1" + redent "^3.0.0" + trim-newlines "^3.0.0" + type-fest "^0.13.1" + yargs-parser "^18.1.3" + merge-deep@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/merge-deep/-/merge-deep-3.0.2.tgz#f39fa100a4f1bd34ff29f7d2bf4508fbb8d83ad2" @@ -8311,6 +8563,11 @@ mimic-fn@^2.0.0, mimic-fn@^2.1.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== +mimic-response@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" + integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== + min-indent@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" @@ -8351,6 +8608,15 @@ minimatch@3.0.4, minimatch@^3.0.4: dependencies: brace-expansion "^1.1.7" +minimist-options@^4.0.2: + version "4.1.0" + resolved "https://registry.yarnpkg.com/minimist-options/-/minimist-options-4.1.0.tgz#c0655713c53a8a2ebd77ffa247d342c40f010619" + integrity sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A== + dependencies: + arrify "^1.0.1" + is-plain-obj "^1.1.0" + kind-of "^6.0.3" + minimist@^1.1.1, minimist@^1.2.0, minimist@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" @@ -8491,6 +8757,11 @@ nan@^2.12.1: resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.1.tgz#d7be34dfa3105b91494c3147089315eff8874b01" integrity sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw== +nanoid@^2.0.1: + version "2.1.11" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-2.1.11.tgz#ec24b8a758d591561531b4176a01e3ab4f0f0280" + integrity sha512-s/snB+WGm6uwi0WjsZdaVcuf3KJXlfGl2LcxgwkEwJF0D/BWzVWAZW/XY4bFaiR7s0Jk3FPvlnepg1H1b1UwlA== + nanomatch@^1.2.9: version "1.2.13" resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" @@ -8601,7 +8872,7 @@ node-releases@^1.1.52, node-releases@^1.1.60: resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.60.tgz#6948bdfce8286f0b5d0e5a88e8384e954dfe7084" integrity sha512-gsO4vjEdQaTusZAEebUWp2a5d7dF5DYoIpDG7WySnk7BuZDW+GPpHXoXXuYawRBr/9t5q54tirPz79kFIWg4dA== -normalize-package-data@^2.3.2: +normalize-package-data@^2.3.2, normalize-package-data@^2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== @@ -8638,6 +8909,15 @@ normalize-url@1.9.1: query-string "^4.1.0" sort-keys "^1.0.0" +normalize-url@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-2.0.1.tgz#835a9da1551fa26f70e92329069a23aa6574d7e6" + integrity sha512-D6MUW4K/VzoJ4rJ01JFKxDrtY1v9wrgzCX5f2qj/lzH1m/lW6MhUZFKerVsnyjOhOsYzI9Kqqak+10l4LvLpMw== + dependencies: + prepend-http "^2.0.0" + query-string "^5.0.1" + sort-keys "^2.0.0" + normalize-url@^3.0.0: version "3.3.0" resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-3.3.0.tgz#b2e1c4dc4f7c6d57743df733a4f5978d18650559" @@ -8891,6 +9171,11 @@ os-tmpdir@~1.0.2: resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= +p-cancelable@^0.4.0: + version "0.4.1" + resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-0.4.1.tgz#35f363d67d52081c8d9585e37bcceb7e0bbcb2a0" + integrity sha512-HNa1A8LvB1kie7cERyy21VNeHb2CWJJYqyyC2o3klWFfMGlFmWv2Z7sFgZH8ZiaYL95ydToKTFVXgMV/Os0bBQ== + p-defer@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-1.0.0.tgz#9f6eb182f6c9aa8cd743004a7d4f96b196b0fb0c" @@ -8908,6 +9193,11 @@ p-finally@^1.0.0: resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= +p-is-promise@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-1.1.0.tgz#9c9456989e9f6588017b0434d56097675c3da05e" + integrity sha1-nJRWmJ6fZYgBewQ01WCXZ1w9oF4= + p-is-promise@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-2.1.0.tgz#918cebaea248a62cf7ffab8e3bca8c5f882fc42e" @@ -8979,6 +9269,13 @@ p-retry@^3.0.1: dependencies: retry "^0.12.0" +p-timeout@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-2.0.1.tgz#d8dd1979595d2dc0139e1fe46b8b646cb3cdf038" + integrity sha512-88em58dDVB/KzPEx1X0N3LwFfYZPyDc4B6eF38M1rk9VTZMbxXXgjugz8mmwpS9Ox4BDZ+t6t3QP5+/gazweIA== + dependencies: + p-finally "^1.0.0" + p-try@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" @@ -9989,6 +10286,11 @@ prepend-http@^1.0.0: resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw= +prepend-http@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" + integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc= + prettier-eslint-cli@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/prettier-eslint-cli/-/prettier-eslint-cli-5.0.0.tgz#3d2334053f87413842c1729ecfb7886377bef89f" @@ -10259,6 +10561,15 @@ query-string@^4.1.0: object-assign "^4.1.0" strict-uri-encode "^1.0.0" +query-string@^5.0.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/query-string/-/query-string-5.1.1.tgz#a78c012b71c17e05f2e3fa2319dd330682efb3cb" + integrity sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw== + dependencies: + decode-uri-component "^0.2.0" + object-assign "^4.1.0" + strict-uri-encode "^1.0.0" + querystring-es3@^0.2.0: version "0.2.1" resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73" @@ -10413,6 +10724,15 @@ react-is@^16.12.0, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.0, react-i resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== +react-map-gl-geocoder@^2.0.16: + version "2.0.16" + resolved "https://registry.yarnpkg.com/react-map-gl-geocoder/-/react-map-gl-geocoder-2.0.16.tgz#721e267f1a510a0764873f47677d932884614b8e" + integrity sha512-OXQ3fxv1LxaNn92kQNotEXePNPDVMhhU5hoRH42KkR87wRD3jxZLhFKRXew2LShRPoApzDqJ3E+ntSALzkiThg== + dependencies: + "@mapbox/mapbox-gl-geocoder" "4.7.0" + prop-types "^15.7.2" + viewport-mercator-project "6.1.1" + react-map-gl@^5.2.7: version "5.2.7" resolved "https://registry.yarnpkg.com/react-map-gl/-/react-map-gl-5.2.7.tgz#a3d6eda15ee8abb87b9acd855a0f6996cc364188" @@ -10586,6 +10906,15 @@ read-pkg-up@^4.0.0: find-up "^3.0.0" read-pkg "^3.0.0" +read-pkg-up@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-7.0.1.tgz#f3a6135758459733ae2b95638056e1854e7ef507" + integrity sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg== + dependencies: + find-up "^4.1.0" + read-pkg "^5.2.0" + type-fest "^0.8.1" + read-pkg@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-2.0.0.tgz#8ef1c0623c6a6db0dc6713c4bfac46332b2368f8" @@ -10604,6 +10933,16 @@ read-pkg@^3.0.0: normalize-package-data "^2.3.2" path-type "^3.0.0" +read-pkg@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-5.2.0.tgz#7bf295438ca5a33e56cd30e053b34ee7250c93cc" + integrity sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg== + dependencies: + "@types/normalize-package-data" "^2.4.0" + normalize-package-data "^2.5.0" + parse-json "^5.0.0" + type-fest "^0.6.0" + "readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.6, readable-stream@~2.3.6: version "2.3.7" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" @@ -10928,6 +11267,13 @@ resolve@^1.10.0, resolve@^1.12.0, resolve@^1.13.1, resolve@^1.15.1, resolve@^1.1 dependencies: path-parse "^1.0.6" +responselike@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7" + integrity sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec= + dependencies: + lowercase-keys "^1.0.0" + restore-cursor@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" @@ -11452,6 +11798,13 @@ sort-keys@^1.0.0: dependencies: is-plain-obj "^1.0.0" +sort-keys@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-2.0.0.tgz#658535584861ec97d730d6cf41822e1f56684128" + integrity sha1-ZYU1WEhh7JfXMNbPQYIuH1ZoQSg= + dependencies: + is-plain-obj "^1.0.0" + source-list-map@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34" @@ -11857,6 +12210,19 @@ stylehacks@^4.0.0: postcss "^7.0.0" postcss-selector-parser "^3.0.0" +subtag@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/subtag/-/subtag-0.5.0.tgz#1728a8df5257fb98ded4fb981b2ac7af10cf58ba" + integrity sha512-CaIBcTSb/nyk4xiiSOtZYz1B+F12ZxW8NEp54CdT+84vmh/h4sUnHGC6+KQXUfED8u22PQjCYWfZny8d2ELXwg== + +suggestions@^1.6.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/suggestions/-/suggestions-1.7.0.tgz#63965229c8082e72cfeb08ad426a6f725ab036ba" + integrity sha512-Px+gellrEQUkgM3Lc0Umnz4JIammE0CLcp+7lbNQH/wqnD0u/N1bOXytNOR3Ap1dIZDHE8lYMuwd60jMO6BPDw== + dependencies: + fuzzy "^0.1.1" + xtend "^4.0.0" + supercluster@^7.1.0: version "7.1.0" resolved "https://registry.yarnpkg.com/supercluster/-/supercluster-7.1.0.tgz#f0a457426ec0ab95d69c5f03b51e049774b94479" @@ -12016,6 +12382,11 @@ thunky@^1.0.2: resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.1.0.tgz#5abaf714a9405db0504732bbccd2cedd9ef9537d" integrity sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA== +timed-out@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f" + integrity sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8= + timers-browserify@^2.0.4: version "2.0.11" resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.11.tgz#800b1f3eee272e5bc53ee465a04d0e804c31211f" @@ -12133,6 +12504,11 @@ tr46@^2.0.2: dependencies: punycode "^2.1.1" +trim-newlines@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-3.0.0.tgz#79726304a6a898aa8373427298d54c2ee8b1cb30" + integrity sha512-C4+gOpvmxaSMKuEf9Qc134F1ZuOHVXKRbtEflf4NTtuuJDEIJ9p5PXsalL8SkeRw+qit1Mo+yuvMPAKwWg/1hA== + ts-pnp@1.1.6: version "1.1.6" resolved "https://registry.yarnpkg.com/ts-pnp/-/ts-pnp-1.1.6.tgz#389a24396d425a0d3162e96d2b4638900fdc289a" @@ -12199,6 +12575,16 @@ type-fest@^0.11.0: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.11.0.tgz#97abf0872310fed88a5c466b25681576145e33f1" integrity sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ== +type-fest@^0.13.1: + version "0.13.1" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.13.1.tgz#0172cb5bce80b0bd542ea348db50c7e21834d934" + integrity sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg== + +type-fest@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b" + integrity sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg== + type-fest@^0.8.1: version "0.8.1" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" @@ -12343,6 +12729,13 @@ url-loader@2.3.0: mime "^2.4.4" schema-utils "^2.5.0" +url-parse-lax@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-3.0.0.tgz#16b5cafc07dbe3676c1b1999177823d6503acb0c" + integrity sha1-FrXK/Afb42dsGxmZF3gj1lA6yww= + dependencies: + prepend-http "^2.0.0" + url-parse@^1.4.3: version "1.4.7" resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.7.tgz#a8a83535e8c00a316e403a5db4ac1b9b853ae278" @@ -12351,6 +12744,11 @@ url-parse@^1.4.3: querystringify "^2.1.1" requires-port "^1.0.0" +url-to-options@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/url-to-options/-/url-to-options-1.0.1.tgz#1505a03a289a48cbd7a434efbaeec5055f5633a9" + integrity sha1-FQWgOiiaSMvXpDTvuu7FBV9WM6k= + url@^0.11.0: version "0.11.0" resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" @@ -12458,6 +12856,14 @@ verror@1.10.0: core-util-is "1.0.2" extsprintf "^1.2.0" +viewport-mercator-project@6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/viewport-mercator-project/-/viewport-mercator-project-6.1.1.tgz#d7b2cb3cb772b819f1daab17cf4019102a9102a6" + integrity sha512-nI0GEmXnESwZxWSJuaQkdCnvOv6yckUfqqFbNB8KWVbQY3eUExVM4ZziqCVVs5mNznLjDF1auj6HLW5D5DKcng== + dependencies: + "@babel/runtime" "^7.0.0" + gl-matrix "^3.0.0" + "viewport-mercator-project@^6.2.3 || ^7.0.1": version "7.0.1" resolved "https://registry.yarnpkg.com/viewport-mercator-project/-/viewport-mercator-project-7.0.1.tgz#9d7248072f2cbb122f93b63d2b346a5763b8d79a" @@ -12997,7 +13403,7 @@ xregexp@^4.3.0: dependencies: "@babel/runtime-corejs3" "^7.8.3" -xtend@^4.0.0, xtend@~4.0.1: +xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== @@ -13038,6 +13444,14 @@ yargs-parser@^13.1.2: camelcase "^5.0.0" decamelize "^1.2.0" +yargs-parser@^18.1.3: + version "18.1.3" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0" + integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ== + dependencies: + camelcase "^5.0.0" + decamelize "^1.2.0" + yargs@12.0.5: version "12.0.5" resolved "https://registry.yarnpkg.com/yargs/-/yargs-12.0.5.tgz#05f5997b609647b64f66b81e3b4b10a368e7ad13" From 3422b0730ddddbf9812c2d73eb4d3879cd3a7eab Mon Sep 17 00:00:00 2001 From: Jason Lampel Date: Sat, 29 Aug 2020 15:39:55 -0600 Subject: [PATCH 02/18] Trivial refactoring: filter features by ID fn into utils --- src/components/map/Map.tsx | 30 +----------------------------- src/components/map/utils.ts | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 29 deletions(-) diff --git a/src/components/map/Map.tsx b/src/components/map/Map.tsx index f3111fb1..5ad1f8e8 100644 --- a/src/components/map/Map.tsx +++ b/src/components/map/Map.tsx @@ -80,35 +80,8 @@ export const Map: FC = ({ const map: MbMap = mapRef.current.getMap() const currentLayerNames = state.legendItems.map((item) => item.legendLabel) - // TODO: consider usefulness, otherwise remove - // const layer = map.getLayer(name) - currentLayerNames.forEach((name) => { - const currentFilters = map.getFilter(name) - - // Clear it first // TODO: rm if not necessary - map.setFilter(name, null) - - let origFilter = [] - - // GROSS dude. Gotta be a better way to check? - if (currentFilters[0] === 'all') { - ;[, origFilter] = currentFilters - } else { - origFilter = currentFilters - } - - if (langFeatIDs === null) { - map.setFilter(name, origFilter) - } else { - map.setFilter(name, [ - 'all', - origFilter, - // CRED: https://gis.stackexchange.com/a/287629/5824 - ['in', ['get', 'ID'], ['literal', langFeatIDs]], - ]) - } - }) + mapUtils.filterLayersByFeatIDs(map, currentLayerNames, langFeatIDs) }, [langFeatIDs]) // TODO: put in... legend? @@ -260,7 +233,6 @@ export const Map: FC = ({ map.addControl(new AttributionControl({ compact: false }), 'bottom-right') } - // TODO: chuck it into utils function onNativeClick(event: MapTypes.MapEvent): void { // Map not ready if (!mapRef || !mapRef.current || !mapLoaded) { diff --git a/src/components/map/utils.ts b/src/components/map/utils.ts index f9da2ed6..76f73766 100644 --- a/src/components/map/utils.ts +++ b/src/components/map/utils.ts @@ -120,3 +120,38 @@ export const addLangTypeIconsToMap = ( img.src = icon }) } + +export const filterLayersByFeatIDs = ( + map: Map, + layerNames: string[], + langFeatIDs: null | number[] +): void => { + layerNames.forEach((name) => { + const currentFilters = map.getFilter(name) + + // Clear it first // TODO: rm if not necessary + map.setFilter(name, null) + + let origFilter = [] + + // TODO: consider usefulness, otherwise remove: + // const layer = map.getLayer(name) + // GROSS dude. Gotta be a better way to check? + if (currentFilters[0] === 'all') { + ;[, origFilter] = currentFilters + } else { + origFilter = currentFilters + } + + if (langFeatIDs === null) { + map.setFilter(name, origFilter) + } else { + map.setFilter(name, [ + 'all', + origFilter, + // CRED: https://gis.stackexchange.com/a/287629/5824 + ['in', ['get', 'ID'], ['literal', langFeatIDs]], + ]) + } + }) +} From 2d06b5800815558dfc9b447df3a1f8768349276a Mon Sep 17 00:00:00 2001 From: Jason Lampel Date: Sat, 29 Aug 2020 17:25:36 -0600 Subject: [PATCH 03/18] =?UTF-8?q?Geocode:=20put=20inside=20Popover,=20flyT?= =?UTF-8?q?o=20wired=20up=20=F0=9F=92=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/map/Map.tsx | 2 +- src/components/map/MapCtrlBtns.tsx | 117 ++++++++++++++--------------- src/components/map/config.ts | 2 + src/components/map/types.ts | 20 ++++- 4 files changed, 79 insertions(+), 62 deletions(-) diff --git a/src/components/map/Map.tsx b/src/components/map/Map.tsx index 5ad1f8e8..04e7c0d9 100644 --- a/src/components/map/Map.tsx +++ b/src/components/map/Map.tsx @@ -365,8 +365,8 @@ export const Map: FC = ({ { onMapCtrlClick(actionID) }} diff --git a/src/components/map/MapCtrlBtns.tsx b/src/components/map/MapCtrlBtns.tsx index 70944427..9b9478a1 100644 --- a/src/components/map/MapCtrlBtns.tsx +++ b/src/components/map/MapCtrlBtns.tsx @@ -1,6 +1,6 @@ -import React, { FC, useCallback } from 'react' +import React, { FC } from 'react' import { makeStyles, createStyles, Theme } from '@material-ui/core/styles' -import { InteractiveMap, ViewportProps } from 'react-map-gl' +import { Popover, Box } from '@material-ui/core' import Geocoder from 'react-map-gl-geocoder' import { SpeedDial, @@ -14,23 +14,9 @@ import { FiHome, FiZoomIn, FiZoomOut, FiInfo } from 'react-icons/fi' import 'react-map-gl-geocoder/dist/mapbox-gl-geocoder.css' -import { MapControlAction } from './types' -import { MAPBOX_TOKEN } from './config' - -type MapCtrlBtnsProps = { - // Render prop so we don't have pass a million props to this component - onMapCtrlClick: (actionID: MapControlAction) => void - isDesktop: boolean - setViewport: React.Dispatch - mapRef: React.RefObject -} - -type CtrlBtnConfig = { - id: MapControlAction - icon: React.ReactNode - name: string - customFn?: boolean -} +import { flyToCoords } from './utils' +import { MapCtrlBtnsProps, CtrlBtnConfig, GeocodeResult } from './types' +import { MAPBOX_TOKEN, NYC_LAT_LONG } from './config' const useStyles = makeStyles((theme: Theme) => createStyles({ @@ -56,6 +42,9 @@ const useStyles = makeStyles((theme: Theme) => }, }, }, + layersMenuPaper: { + overflow: 'visible', + }, }) ) @@ -73,12 +62,16 @@ const ctrlBtnsConfig = [ ] as CtrlBtnConfig[] export const MapCtrlBtns: FC = (props) => { - const { isDesktop, onMapCtrlClick, mapRef, setViewport } = props + const { isDesktop, onMapCtrlClick, mapRef, mapOffset } = props const classes = useStyles() const [speedDialOpen, setSpeedDialOpen] = React.useState(true) - const [locSearchOpen, setLocSearchOpen] = React.useState(false) const size = isDesktop ? 'medium' : 'small' const geocoderContainerRef = React.useRef(null) + const [anchorEl, setAnchorEl] = React.useState(null) + const layersMenuOpen = Boolean(anchorEl) + + const handleSpeedDialRootClick = () => setSpeedDialOpen(!speedDialOpen) + const handleLayersMenuClose = () => setAnchorEl(null) const handleClose = ( e: React.SyntheticEvent, Event>, @@ -94,46 +87,50 @@ export const MapCtrlBtns: FC = (props) => { setSpeedDialOpen(false) } - const handleRootClick = () => { - setSpeedDialOpen(!speedDialOpen) - } - - const handleGeocoderViewportChange = useCallback((newViewport) => { - const geocoderDefaultOverrides = { transitionDuration: 1000 } - - return handleViewportChange({ - ...newViewport, - ...geocoderDefaultOverrides, - }) - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []) - - const handleViewportChange = useCallback( - (newViewport) => setViewport(newViewport), - // eslint-disable-next-line react-hooks/exhaustive-deps - [] - ) - return ( <> -
- -
+ +
+ { + handleLayersMenuClose() + + if (mapRef.current) { + flyToCoords( + mapRef.current.getMap(), + { + latitude: geocodeResult.result.center[1], + longitude: geocodeResult.result.center[0], + zoom: 14, + }, + mapOffset, + null + ) + } + }} + /> + + = (props) => { open={speedDialOpen} direction="down" FabProps={{ size }} - onClick={handleRootClick} + onClick={handleSpeedDialRootClick} > {ctrlBtnsConfig.map((action) => ( = (props) => { key={action.name} icon={action.icon} tooltipTitle={action.name} + FabProps={{ size }} // TODO: uhhhh breakpoints? Why is this needed? onClick={(e) => { e.stopPropagation() // prevent closing the menu @@ -157,10 +155,9 @@ export const MapCtrlBtns: FC = (props) => { if (!action.customFn) { onMapCtrlClick(action.id) } else { - setLocSearchOpen(!locSearchOpen) + setAnchorEl(e.currentTarget) } }} - FabProps={{ size }} // TODO: uhhhh breakpoints? Why is this needed? /> ))} diff --git a/src/components/map/config.ts b/src/components/map/config.ts index 08fe19b4..0004010a 100644 --- a/src/components/map/config.ts +++ b/src/components/map/config.ts @@ -27,6 +27,8 @@ export const mbStyleTileConfig = { }, } +export const NYC_LAT_LONG = { latitude: 40.7128, longitude: -74.006 } + // TODO: rm if only using local, otherwise restore when ready // export const symbStyleUrl = `${MB_STYLES_API_URL}/${mbStyleTileConfig.styleUrl}?access_token=${MAPBOX_TOKEN}` // Unsure why it needs the type here but not for feature coords.. diff --git a/src/components/map/types.ts b/src/components/map/types.ts index 141597f6..07f921f4 100644 --- a/src/components/map/types.ts +++ b/src/components/map/types.ts @@ -1,4 +1,4 @@ -import { PointerEvent, LayerProps } from 'react-map-gl' +import { PointerEvent, InteractiveMap, LayerProps } from 'react-map-gl' import { CircleLayout, CirclePaint, @@ -117,3 +117,21 @@ export type UseStyleProps = { panelOpen: boolean screenHeight: number } + +export type GeocodeResult = { + result: { center: [number, number] } +} + +export type MapCtrlBtnsProps = { + isDesktop: boolean + mapOffset: [number, number] + mapRef: React.RefObject + onMapCtrlClick: (actionID: MapControlAction) => void +} + +export type CtrlBtnConfig = { + id: MapControlAction + icon: React.ReactNode + name: string + customFn?: boolean +} From 4b213079f4fa7a2146960a257b9dc2ca97508ce8 Mon Sep 17 00:00:00 2001 From: Jason Lampel Date: Sat, 29 Aug 2020 18:29:15 -0600 Subject: [PATCH 04/18] Trivial map refactor --- src/components/map/Map.tsx | 10 ++++----- src/components/map/MapCtrlBtns.tsx | 36 +++++++++++++++++------------- src/components/map/index.ts | 4 ---- 3 files changed, 24 insertions(+), 26 deletions(-) diff --git a/src/components/map/Map.tsx b/src/components/map/Map.tsx index 04e7c0d9..906ce62a 100644 --- a/src/components/map/Map.tsx +++ b/src/components/map/Map.tsx @@ -7,13 +7,11 @@ import { useTheme } from '@material-ui/core/styles' import 'mapbox-gl/dist/mapbox-gl.css' import { GlobalContext, LoadingBackdrop } from 'components' -import { - LangMbSrcAndLayer, - MapPopup, - MapTooltip, - MapCtrlBtns, -} from 'components/map' import { initLegend } from 'components/legend/utils' +import { LangMbSrcAndLayer } from './LangMbSrcAndLayer' +import { MapPopup } from './MapPopup' +import { MapTooltip } from './MapTooltip' +import { MapCtrlBtns } from './MapCtrlBtns' import * as MapTypes from './types' import * as mapUtils from './utils' import * as mapConfig from './config' diff --git a/src/components/map/MapCtrlBtns.tsx b/src/components/map/MapCtrlBtns.tsx index 9b9478a1..c065de72 100644 --- a/src/components/map/MapCtrlBtns.tsx +++ b/src/components/map/MapCtrlBtns.tsx @@ -73,6 +73,23 @@ export const MapCtrlBtns: FC = (props) => { const handleSpeedDialRootClick = () => setSpeedDialOpen(!speedDialOpen) const handleLayersMenuClose = () => setAnchorEl(null) + const handleGeocodeResult = (geocodeResult: GeocodeResult) => { + handleLayersMenuClose() + + if (mapRef.current) { + flyToCoords( + mapRef.current.getMap(), + { + latitude: geocodeResult.result.center[1], + longitude: geocodeResult.result.center[0], + zoom: 14, + }, + mapOffset, + null + ) + } + } + const handleClose = ( e: React.SyntheticEvent, Event>, reason: CloseReason @@ -107,27 +124,14 @@ export const MapCtrlBtns: FC = (props) => { { - handleLayersMenuClose() - - if (mapRef.current) { - flyToCoords( - mapRef.current.getMap(), - { - latitude: geocodeResult.result.center[1], - longitude: geocodeResult.result.center[0], - zoom: 14, - }, - mapOffset, - null - ) - } - }} + onResult={handleGeocodeResult} /> diff --git a/src/components/map/index.ts b/src/components/map/index.ts index b4cfaf4e..625c8bdd 100644 --- a/src/components/map/index.ts +++ b/src/components/map/index.ts @@ -1,8 +1,4 @@ -export * from './LangMbSrcAndLayer' export * from './LayersMenu' export * from './LayerToggle' export * from './Map' -export * from './MapCtrlBtns' -export * from './MapPopup' -export * from './MapTooltip' export * from './MapWrap' From 29cf487ab2e6a0c849952afd735c960bb5471fb6 Mon Sep 17 00:00:00 2001 From: Jason Lampel Date: Sat, 29 Aug 2020 18:58:47 -0600 Subject: [PATCH 05/18] More cleanup, rm unused junk --- src/components/details/DetailsPanel.tsx | 35 +++++------- src/components/map/LayerToggle.tsx | 33 ------------ src/components/map/LayersMenu.tsx | 71 ------------------------- src/components/map/MapCtrlBtns.tsx | 8 ++- src/components/map/index.ts | 2 - src/context/initialState.ts | 5 -- src/context/reducer.tsx | 9 ---- src/context/types.ts | 10 ---- 8 files changed, 19 insertions(+), 154 deletions(-) delete mode 100644 src/components/map/LayerToggle.tsx delete mode 100644 src/components/map/LayersMenu.tsx diff --git a/src/components/details/DetailsPanel.tsx b/src/components/details/DetailsPanel.tsx index 3eecd5be..28456d54 100644 --- a/src/components/details/DetailsPanel.tsx +++ b/src/components/details/DetailsPanel.tsx @@ -1,10 +1,8 @@ import React, { FC, useContext } from 'react' -import { Link as RouterLink } from 'react-router-dom' import { createStyles, makeStyles, Theme } from '@material-ui/core/styles' import { Typography, Divider } from '@material-ui/core' -import { RouteLocation } from 'components/map/types' -import { GlobalContext, LoadingIndicator } from 'components' +import { GlobalContext } from 'components' import { isURL, correctDropboxURL, prettyTruncateList } from '../../utils' type EndoImageComponent = { @@ -12,12 +10,10 @@ type EndoImageComponent = { alt: string } -const DATA_TABLE_PATHNAME: RouteLocation = '/table' - const useStyles = makeStyles((theme: Theme) => createStyles({ intro: { - paddingBottom: theme.spacing(1), + padding: '0.5em', textAlign: 'center', }, // Gross but it makes `Anashinaabemowin` fit @@ -52,17 +48,15 @@ const EndoImageWrap: FC = (props) => { } const NoFeatSel: FC = () => { + const classes = useStyles() + return ( - - Click a language community in the map or the{' '} - - data table - - . - +
+ + No community selected. Click a language community in the map or the data + table to see detailed information. + +
) } @@ -72,17 +66,12 @@ export const DetailsPanel: FC = () => { // Shaky check to see if features have loaded and are stored globally // TODO: use MB's loading events to set this instead - if (!state.langFeaturesCached.length) { - // TODO: skeletons - return - } + if (!state.langFeaturesCached.length) return null const { selFeatAttribs } = state // No sel feat details - if (!selFeatAttribs) { - return - } + if (!selFeatAttribs) return const { Endonym, Language, Neighborhoods, Description } = selFeatAttribs const { detailsPanelHeading, intro, description, neighborhoods } = classes diff --git a/src/components/map/LayerToggle.tsx b/src/components/map/LayerToggle.tsx deleted file mode 100644 index 41acfbc0..00000000 --- a/src/components/map/LayerToggle.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import React, { FC, useContext } from 'react' -import { FormControlLabel, Switch } from '@material-ui/core' - -import { GlobalContext } from 'components' -import { LayerVisibility } from 'context/types' - -type LayerToggleComponent = { - name: string - layerId: keyof LayerVisibility -} - -export const LayerToggle: FC = ({ name, layerId }) => { - const { state, dispatch } = useContext(GlobalContext) - const checked = state.layerVisibility[layerId] - - const handleChange = (event: React.ChangeEvent) => { - dispatch({ type: 'TOGGLE_LAYER_VISIBILITY', payload: layerId }) - } - - return ( - - } - /> - ) -} diff --git a/src/components/map/LayersMenu.tsx b/src/components/map/LayersMenu.tsx deleted file mode 100644 index 94b241cd..00000000 --- a/src/components/map/LayersMenu.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import React, { FC, useContext, useState } from 'react' -import { createStyles, makeStyles, Theme } from '@material-ui/core/styles' -import { - FormControl, - FormLabel, - FormControlLabel, - FormGroup, - RadioGroup, - Radio, -} from '@material-ui/core' - -import { GlobalContext } from 'components' -import { LayerToggle } from 'components/map' -import { Baselayer } from './types' - -const useStyles = makeStyles((theme: Theme) => - createStyles({ - formHeading: { - marginBottom: 4, - '&:last-of-type': { - marginTop: theme.spacing(2), - }, - }, - baselayerGroup: { - display: 'flex', - flexDirection: 'row', - }, - }) -) - -export const LayersMenu: FC = () => { - const classes = useStyles() - // TODO: use global state instead? - const [activeBaselayer, setActiveBaselayer] = useState('light') - const { dispatch } = useContext(GlobalContext) - - const handleChange = (event: React.ChangeEvent) => { - const { value } = event.target as HTMLInputElement - setActiveBaselayer(value) - - dispatch({ - type: 'SET_BASELAYER', - payload: value as Baselayer, - }) - } - - return ( - - - Layer Visibility - - - - - - - Background Layer - - - } label="Light" /> - } label="Dark" /> - - - ) -} diff --git a/src/components/map/MapCtrlBtns.tsx b/src/components/map/MapCtrlBtns.tsx index c065de72..e2c03a3e 100644 --- a/src/components/map/MapCtrlBtns.tsx +++ b/src/components/map/MapCtrlBtns.tsx @@ -42,6 +42,12 @@ const useStyles = makeStyles((theme: Theme) => }, }, }, + popoverContent: { + padding: '1em 0.75em', + width: 300, + display: 'flex', + justifyContent: 'center', + }, layersMenuPaper: { overflow: 'visible', }, @@ -119,7 +125,7 @@ export const MapCtrlBtns: FC = (props) => { horizontal: 'right', }} > - +
Date: Sat, 29 Aug 2020 21:19:40 -0600 Subject: [PATCH 06/18] Tons of UI improvements and additions: - Legend swatch size and consistency - Add "World Region" w/swatch to Details intro (ready to go for table) - Style improvements to Details, omnibox, Record Description --- src/components/details/DetailsPanel.tsx | 63 +++++++++++++------- src/components/filters/OmniboxResult.tsx | 3 +- src/components/filters/SearchByOmnibox.tsx | 3 +- src/components/legend/LegendSwatch.tsx | 26 ++++---- src/components/legend/types.ts | 1 + src/components/results/RecordDescription.tsx | 7 ++- src/components/results/index.ts | 1 + src/context/initialState.ts | 1 + src/context/reducer.tsx | 5 ++ src/context/types.ts | 12 +++- src/utils.ts | 16 +++++ 11 files changed, 99 insertions(+), 39 deletions(-) diff --git a/src/components/details/DetailsPanel.tsx b/src/components/details/DetailsPanel.tsx index 28456d54..1e767ead 100644 --- a/src/components/details/DetailsPanel.tsx +++ b/src/components/details/DetailsPanel.tsx @@ -1,9 +1,13 @@ import React, { FC, useContext } from 'react' import { createStyles, makeStyles, Theme } from '@material-ui/core/styles' -import { Typography, Divider } from '@material-ui/core' +import { Typography } from '@material-ui/core' import { GlobalContext } from 'components' +import { LegendSwatch } from 'components/legend' +import { RecordDescription } from 'components/results' import { isURL, correctDropboxURL, prettyTruncateList } from '../../utils' +// TODO: cell strength bars for Size +// import { COMM_SIZE_COL_MAP } from 'components/results/config' type EndoImageComponent = { url: string @@ -13,8 +17,9 @@ type EndoImageComponent = { const useStyles = makeStyles((theme: Theme) => createStyles({ intro: { - padding: '0.5em', + padding: '1em 0', textAlign: 'center', + borderBottom: `solid 1px ${theme.palette.divider}`, }, // Gross but it makes `Anashinaabemowin` fit detailsPanelHeading: { @@ -33,7 +38,12 @@ const useStyles = makeStyles((theme: Theme) => }, description: { fontSize: theme.typography.caption.fontSize, - marginTop: theme.spacing(1), + padding: '0 0.25rem', + marginBottom: '2.4rem', + }, + prettyFlex: { + display: 'flex', + justifyContent: 'center', }, }) ) @@ -48,15 +58,11 @@ const EndoImageWrap: FC = (props) => { } const NoFeatSel: FC = () => { - const classes = useStyles() - return ( -
- - No community selected. Click a language community in the map or the data - table to see detailed information. - -
+ + No community selected. Click a point in the map or a row in the data table + to see detailed information. + ) } @@ -73,9 +79,21 @@ export const DetailsPanel: FC = () => { // No sel feat details if (!selFeatAttribs) return - const { Endonym, Language, Neighborhoods, Description } = selFeatAttribs + const { + Endonym, + Language, + Neighborhoods, + Description, + // Size, // TODO: cell strength bars for Size + Town, + 'World Region': WorldRegion, + } = selFeatAttribs const { detailsPanelHeading, intro, description, neighborhoods } = classes const isImage = isURL(Endonym) + const regionSwatchColor = + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + state.legendSymbols[WorldRegion].paint['circle-color'] // TODO: deal with `id` present in URL but no match found // const parsed = queryString.parse(window.location.search) @@ -83,7 +101,6 @@ export const DetailsPanel: FC = () => { // (feature) => feature.ID === parsed.id // ) - // TODO: something respectable for styles, aka MUI-something return ( <>
@@ -97,15 +114,21 @@ export const DetailsPanel: FC = () => { {Language} )} - {Neighborhoods && ( - - {prettyTruncateList(Neighborhoods)} - - )} + + {Neighborhoods ? prettyTruncateList(Neighborhoods) : Town} + +
+ + {/* TODO: cell strength bars for Size */} +
- - {Description} + ) diff --git a/src/components/filters/OmniboxResult.tsx b/src/components/filters/OmniboxResult.tsx index 5d7ad2dc..ae25e056 100644 --- a/src/components/filters/OmniboxResult.tsx +++ b/src/components/filters/OmniboxResult.tsx @@ -20,7 +20,6 @@ const useStyles = makeStyles((theme: Theme) => color: theme.palette.primary.main, display: 'flex', fontSize: '1em', - fontWeight: 'bold', lineHeight: 1, marginTop: 4, '& svg': { @@ -37,8 +36,8 @@ const useStyles = makeStyles((theme: Theme) => }, }, detailLabel: { - color: theme.palette.text.secondary, fontFamily: theme.typography.h1.fontFamily, + color: theme.palette.text.primary, marginRight: 4, }, detailValue: { diff --git a/src/components/filters/SearchByOmnibox.tsx b/src/components/filters/SearchByOmnibox.tsx index 8442f145..4c4c3e48 100644 --- a/src/components/filters/SearchByOmnibox.tsx +++ b/src/components/filters/SearchByOmnibox.tsx @@ -30,6 +30,7 @@ const useStyles = makeStyles((theme: Theme) => // Group headings '& .MuiListSubheader-root': { borderBottom: `1px solid ${theme.palette.text.hint}`, + color: theme.palette.text.primary, fontFamily: theme.typography.h1.fontFamily, fontSize: '1.1rem', paddingLeft: 12, @@ -37,7 +38,7 @@ const useStyles = makeStyles((theme: Theme) => }, // The
  • items. Not sure why it works via classes and `groupUl` doesn't. option: { - borderBottom: `solid 1px ${theme.palette.text.hint}`, + borderBottom: `solid 1px ${theme.palette.divider}`, display: 'flex', paddingLeft: 12, }, diff --git a/src/components/legend/LegendSwatch.tsx b/src/components/legend/LegendSwatch.tsx index 2ac04eb3..2d4d7035 100644 --- a/src/components/legend/LegendSwatch.tsx +++ b/src/components/legend/LegendSwatch.tsx @@ -1,6 +1,6 @@ import React, { FC } from 'react' import { createStyles, makeStyles, Theme } from '@material-ui/core/styles' -import { Typography } from '@material-ui/core' +import { Typography, Box } from '@material-ui/core' import { LegendSwatchComponent } from './types' @@ -9,17 +9,20 @@ const useStyles = makeStyles((theme: Theme) => legendSwatchRoot: { alignItems: 'center', display: 'grid', - gridTemplateColumns: '24px 1fr', + gridTemplateColumns: 'minmax(1em, auto) 1fr', + gridColumnGap: '0.24em', justifyItems: 'center', marginBottom: (props: { type: 'symbol' | string }) => props.type === 'symbol' ? theme.spacing(1) : 0, }, - swatch: { - marginRight: theme.spacing(1), - }, legendLabel: { justifySelf: 'flex-start', }, + imgSwatch: { + height: 20, + width: 20, + marginRight: '0.2em', + }, }) ) @@ -31,15 +34,15 @@ export const LegendSwatch: FC = ({ type, legendLabel, size = 7, + component = 'li', }) => { const classes = useStyles({ type }) const adjustedSize = size * 1.5 return ( -
  • + {type === 'circle' && ( = ({ /> )} {type === 'symbol' && ( - {legendLabel} + {legendLabel} )} {legendLabel} -
  • + ) } diff --git a/src/components/legend/types.ts b/src/components/legend/types.ts index ef5019aa..4a97bbff 100644 --- a/src/components/legend/types.ts +++ b/src/components/legend/types.ts @@ -11,6 +11,7 @@ export type LegendSwatch = { // Same as the regular swatch but will have SVG element if it is a symbol export type LegendSwatchComponent = LegendSwatch & { icon?: string + component?: React.ElementType } export type LegendGroupConfig = { diff --git a/src/components/results/RecordDescription.tsx b/src/components/results/RecordDescription.tsx index 2ebd6a16..e99ecd6f 100644 --- a/src/components/results/RecordDescription.tsx +++ b/src/components/results/RecordDescription.tsx @@ -9,11 +9,16 @@ const useStyles = makeStyles((theme: Theme) => marginTop: theme.spacing(2), }, firstLetter: { + display: 'inline-block', fontSize: theme.typography.h1.fontSize, fontFamily: theme.typography.h1.fontFamily, fontWeight: theme.typography.h1.fontWeight, lineHeight: 0, marginRight: 4, + marginTop: '1rem', + }, + body: { + fontSize: '0.85em', }, closeBtn: { position: 'absolute', @@ -33,7 +38,7 @@ export const RecordDescription: FC<{ text: string }> = (props) => { {text && ( <> {text[0]} - {text.slice(1)} + {text.slice(1)} )} {!text && 'No description available'} diff --git a/src/components/results/index.ts b/src/components/results/index.ts index 692dd0df..c0770b1d 100644 --- a/src/components/results/index.ts +++ b/src/components/results/index.ts @@ -1,3 +1,4 @@ +export * from './RecordDescription' export * from './ResultsModal' export * from './ResultsTable' export * from './ViewResultsDataBtn' diff --git a/src/context/initialState.ts b/src/context/initialState.ts index 0a9af1f1..f0b5341f 100644 --- a/src/context/initialState.ts +++ b/src/context/initialState.ts @@ -11,6 +11,7 @@ const initialMapStates = { langFeaturesCached: [], langLabels: [], legendItems: [], + legendSymbols: {}, langSymbGroups: {}, } diff --git a/src/context/reducer.tsx b/src/context/reducer.tsx index 06bf857a..22afa082 100644 --- a/src/context/reducer.tsx +++ b/src/context/reducer.tsx @@ -21,6 +21,11 @@ export const reducer = ( ...state, langSymbGroups: action.payload, } + case 'INIT_LEGEND_SYMBOLS': + return { + ...state, + legendSymbols: action.payload, + } case 'SET_BASELAYER': return { ...state, diff --git a/src/context/types.ts b/src/context/types.ts index 8ae6bc18..99a15b56 100644 --- a/src/context/types.ts +++ b/src/context/types.ts @@ -3,15 +3,24 @@ // https://github.com/Covid-Self-report-Tool/cov-self-report-frontend/blob/master/LICENSE import { Color } from '@material-ui/lab/Alert' -import { MetadataGroup, Baselayer } from 'components/map/types' +import { + MetadataGroup, + Baselayer, + LayerPropsNonBGlayer, +} from 'components/map/types' import { LegendSwatch } from 'components/legend/types' export type PanelState = 'default' | 'maximized' | 'minimized' +export type LegendSymbols = { + [key: string]: Partial +} + export type StoreAction = | { type: 'INIT_LANG_LAYER_FEATURES'; payload: LangRecordSchema[] } | { type: 'INIT_LANG_LAYER_LABEL_OPTIONS'; payload: string[] } | { type: 'INIT_LANG_LAYER_SYMB_OPTIONS'; payload: MetadataGroup } + | { type: 'INIT_LEGEND_SYMBOLS'; payload: LegendSymbols } | { type: 'SET_BASELAYER'; payload: Baselayer } | { type: 'SET_LANG_FEAT_IDS'; payload: number[] | null } | { type: 'SET_LANG_LAYER_LABELS'; payload: string } @@ -32,6 +41,7 @@ export type InitialState = { langFeaturesCached: LangRecordSchema[] langLabels: string[] legendItems: LegendSwatch[] + legendSymbols: LegendSymbols langSymbGroups: MetadataGroup mapLoaded: boolean offCanvasNavOpen: boolean diff --git a/src/utils.ts b/src/utils.ts index be6632cf..c0ae4737 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -71,6 +71,22 @@ export const getMbStyleDocument = async ( setLabelLayers(labelsLayers) setSymbLayers(notTheBgLayerOrLabels) + + const legend = notTheBgLayerOrLabels.reduce((all, thisOne) => { + return { + ...all, + [thisOne.id as string]: { + paint: thisOne.paint, + type: thisOne.type, + layout: thisOne.layout, + }, + } + }, {}) + + dispatch({ + type: 'INIT_LEGEND_SYMBOLS', + payload: legend, + }) } export const findFeatureByID = ( From ca11bb47b97dacd0ce51c7cb5e61a3922c105786 Mon Sep 17 00:00:00 2001 From: Jason Lampel Date: Sat, 29 Aug 2020 21:45:06 -0600 Subject: [PATCH 07/18] Details panel: add "random link" feature when no community selected --- src/components/details/DetailsPanel.tsx | 30 ++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/src/components/details/DetailsPanel.tsx b/src/components/details/DetailsPanel.tsx index 1e767ead..c8b93529 100644 --- a/src/components/details/DetailsPanel.tsx +++ b/src/components/details/DetailsPanel.tsx @@ -1,4 +1,5 @@ import React, { FC, useContext } from 'react' +import { Link as RouterLink } from 'react-router-dom' import { createStyles, makeStyles, Theme } from '@material-ui/core/styles' import { Typography } from '@material-ui/core' @@ -57,11 +58,38 @@ const EndoImageWrap: FC = (props) => { return {alt} } +const RandomLink: FC = () => { + const { state } = useContext(GlobalContext) + const { langFeatIDs, langFeaturesCached } = state + + if (langFeatIDs && !langFeatIDs.length) { + return null + } + + let randoIndex = 1 + let id = 1 + + if (langFeatIDs === null) { + randoIndex = Math.floor(Math.random() * langFeaturesCached?.length) + 1 + id = langFeaturesCached[randoIndex].ID + } else { + randoIndex = Math.floor(Math.random() * langFeatIDs?.length) + 1 + id = langFeatIDs[randoIndex] + } + + return ( + <> + , or{' '} + try a random community + + ) +} + const NoFeatSel: FC = () => { return ( No community selected. Click a point in the map or a row in the data table - to see detailed information. + . ) } From f186b3d01e5ef976be0da43b4e9979f1d1bdb9fd Mon Sep 17 00:00:00 2001 From: Jason Lampel Date: Sat, 29 Aug 2020 22:19:08 -0600 Subject: [PATCH 08/18] Location Search popover: improve UI, add dummy zoom-current btn --- src/components/map/MapCtrlBtns.tsx | 79 ++++++++++++++++++-- src/components/results/RecordDescription.tsx | 4 +- 2 files changed, 75 insertions(+), 8 deletions(-) diff --git a/src/components/map/MapCtrlBtns.tsx b/src/components/map/MapCtrlBtns.tsx index e2c03a3e..022d9dda 100644 --- a/src/components/map/MapCtrlBtns.tsx +++ b/src/components/map/MapCtrlBtns.tsx @@ -1,6 +1,6 @@ import React, { FC } from 'react' import { makeStyles, createStyles, Theme } from '@material-ui/core/styles' -import { Popover, Box } from '@material-ui/core' +import { Link, Button, Typography, Popover, Box } from '@material-ui/core' import Geocoder from 'react-map-gl-geocoder' import { SpeedDial, @@ -43,14 +43,23 @@ const useStyles = makeStyles((theme: Theme) => }, }, popoverContent: { - padding: '1em 0.75em', + padding: '1em', width: 300, - display: 'flex', - justifyContent: 'center', + }, + popoverContentHeading: { + marginTop: '.5rem', + }, + popoverContentText: { + marginBottom: '.75em', + fontSize: '0.8em', }, layersMenuPaper: { overflow: 'visible', }, + locationBtnWrap: { + display: 'flex', + justifyContent: 'center', + }, }) ) @@ -67,6 +76,62 @@ const ctrlBtnsConfig = [ { id: 'info', icon: , name: 'About & Info' }, ] as CtrlBtnConfig[] +export const LocationSearchContent: FC = (props) => { + const { children } = props + const classes = useStyles() + + return ( + + + Location Search + + + Search by place + + + + Address, city, neighborhood, postal code, or landmark/placename.{' '} + + @Ross maybe others? + + + + {children} + + My Location + + + + Search by your current location (your info will not be stored or + shared in any way). + + +
    + +
    +
    + ) +} + export const MapCtrlBtns: FC = (props) => { const { isDesktop, onMapCtrlClick, mapRef, mapOffset } = props const classes = useStyles() @@ -88,7 +153,7 @@ export const MapCtrlBtns: FC = (props) => { { latitude: geocodeResult.result.center[1], longitude: geocodeResult.result.center[0], - zoom: 14, + zoom: 13, }, mapOffset, null @@ -125,7 +190,7 @@ export const MapCtrlBtns: FC = (props) => { horizontal: 'right', }} > - +
    = (props) => { proximity={NYC_LAT_LONG} onResult={handleGeocodeResult} /> - + = (props) => { {text.slice(1)} )} - {!text && 'No description available'} + {!text && ( +
    No description available
    + )} ) } From 2c9e6608b856a317a0e4fd6a65522717982aa4af Mon Sep 17 00:00:00 2001 From: Jason Lampel Date: Sun, 30 Aug 2020 13:04:59 -0600 Subject: [PATCH 09/18] MB styles config: add more custom font instances for South(ern/eastern) Asia --- public/data/mb-styles.langs.json | 50 ++++++++++++++++++++++++++++++-- 1 file changed, 48 insertions(+), 2 deletions(-) diff --git a/public/data/mb-styles.langs.json b/public/data/mb-styles.langs.json index dd8377a1..f356933a 100644 --- a/public/data/mb-styles.langs.json +++ b/public/data/mb-styles.langs.json @@ -204,11 +204,33 @@ ["get", "Language"], [ "case", + ["==", ["var", "lang"], "Khmer"], + ["literal", ["Noto Sans Khmer Regular"]], ["==", ["var", "lang"], "Balti"], ["literal", ["Noto Sans Tibetan Regular"]], + ["==", ["var", "lang"], "Chocha-ngacha"], + ["literal", ["Noto Sans Tibetan Regular"]], + ["==", ["var", "lang"], "Dolpo"], + ["literal", ["Noto Sans Tibetan Regular"]], ["==", ["var", "lang"], "Dzongkha"], ["literal", ["Noto Sans Tibetan Regular"]], - ["==", ["var", "lang"], "Tibetan"], + ["==", ["var", "lang"], "Kyirong"], + ["literal", ["Noto Sans Tibetan Regular"]], + ["==", ["var", "lang"], "Ladakhi"], + ["literal", ["Noto Sans Tibetan Regular"]], + ["==", ["var", "lang"], "Limi"], + ["literal", ["Noto Sans Tibetan Regular"]], + ["==", ["var", "lang"], "Loke"], + ["literal", ["Noto Sans Tibetan Regular"]], + ["==", ["var", "lang"], "Mugu"], + ["literal", ["Noto Sans Tibetan Regular"]], + ["==", ["var", "lang"], "Nubri"], + ["literal", ["Noto Sans Tibetan Regular"]], + ["==", ["var", "lang"], "Purgi"], + ["literal", ["Noto Sans Tibetan Regular"]], + ["==", ["var", "lang"], "Seke"], + ["literal", ["Noto Sans Tibetan Regular"]], + ["==", ["var", "lang"], "Sherpa"], ["literal", ["Noto Sans Tibetan Regular"]], ["==", ["var", "lang"], "Tibetan (Amdo)"], ["literal", ["Noto Sans Tibetan Regular"]], @@ -220,6 +242,16 @@ ["literal", ["Noto Sans Tibetan Regular"]], ["==", ["var", "lang"], "Tibetan (Ü-tsang)"], ["literal", ["Noto Sans Tibetan Regular"]], + ["==", ["var", "lang"], "Tibetan"], + ["literal", ["Noto Sans Tibetan Regular"]], + ["==", ["var", "lang"], "Tokpe Gola"], + ["literal", ["Noto Sans Tibetan Regular"]], + ["==", ["var", "lang"], "Tsum"], + ["literal", ["Noto Sans Tibetan Regular"]], + ["==", ["var", "lang"], "Walung"], + ["literal", ["Noto Sans Tibetan Regular"]], + ["==", ["var", "lang"], "Yolmo"], + ["literal", ["Noto Sans Tibetan Regular"]], ["==", ["var", "lang"], "Buginese"], ["literal", ["Noto Sans Buginese Regular"]], ["==", ["var", "lang"], "Makassarese"], @@ -230,12 +262,26 @@ ["literal", ["Noto Sans Limbu Regular"]], ["==", ["var", "lang"], "Shan"], ["literal", ["Noto Sans Myanmar Regular"]], + ["==", ["var", "lang"], "Mon"], + ["literal", ["Noto Sans Myanmar Regular"]], + ["==", ["var", "lang"], "Rakhine"], + ["literal", ["Noto Sans Myanmar Regular"]], + ["==", ["var", "lang"], "Burmese"], + ["literal", ["Noto Sans Myanmar Regular"]], + ["==", ["var", "lang"], "Burmese (Intha)"], + ["literal", ["Noto Sans Myanmar Regular"]], + ["==", ["var", "lang"], "Burmese (Dawei)"], + ["literal", ["Noto Sans Myanmar Regular"]], ["==", ["var", "lang"], "Mandinka"], ["literal", ["Noto Sans NKo Regular"]], ["==", ["var", "lang"], "Sylheti"], ["literal", ["Noto Sans Syloti Nagri Regular"]], + ["==", ["var", "lang"], "Syriac"], + ["literal", ["Noto Sans Syriac Estrangela Regular"]], ["==", ["var", "lang"], "Neo-Aramaic"], - ["literal", ["Noto Sans Syriac Estrangala Regular"]], + ["literal", ["Noto Sans Syriac Estrangela Regular"]], + ["==", ["var", "lang"], "Syriac (Syro-Malankara)"], + ["literal", ["Noto Sans Syriac Estrangela Regular"]], ["==", ["var", "lang"], "Dhivehi"], ["literal", ["Noto Sans Thaana Regular"]], ["==", ["var", "lang"], "Tarifit"], From 391ec1e436dfaad895d75fb61b2f78574ce663f3 Mon Sep 17 00:00:00 2001 From: Jason Lampel Date: Sun, 30 Aug 2020 13:05:19 -0600 Subject: [PATCH 10/18] Country flags: include North Macedonia --- src/components/results/config.emojis.json | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/results/config.emojis.json b/src/components/results/config.emojis.json index b9d5c4ba..8b48220f 100644 --- a/src/components/results/config.emojis.json +++ b/src/components/results/config.emojis.json @@ -170,6 +170,7 @@ "Niue": "NU", "Norfolk Island": "NF", "North Korea": "KP", + "North Macedonia": "MK", "Northern Mariana Islands": "MP", "Norway": "NO", "Oman": "OM", From 96a55b0f5d54d845daf60509520ff29d0c0a1097 Mon Sep 17 00:00:00 2001 From: Jason Lampel Date: Sun, 30 Aug 2020 13:36:33 -0600 Subject: [PATCH 11/18] Loc. search: rm geolocation for now, improve wording --- src/components/map/MapCtrlBtns.tsx | 50 +++++------------------------- 1 file changed, 8 insertions(+), 42 deletions(-) diff --git a/src/components/map/MapCtrlBtns.tsx b/src/components/map/MapCtrlBtns.tsx index 022d9dda..867bc594 100644 --- a/src/components/map/MapCtrlBtns.tsx +++ b/src/components/map/MapCtrlBtns.tsx @@ -1,6 +1,6 @@ import React, { FC } from 'react' import { makeStyles, createStyles, Theme } from '@material-ui/core/styles' -import { Link, Button, Typography, Popover, Box } from '@material-ui/core' +import { Typography, Popover, Box } from '@material-ui/core' import Geocoder from 'react-map-gl-geocoder' import { SpeedDial, @@ -82,52 +82,18 @@ export const LocationSearchContent: FC = (props) => { return ( - - Location Search - - - Search by place + + Search by location - Address, city, neighborhood, postal code, or landmark/placename.{' '} - - @Ross maybe others? - + Enter an address, municipality, neighborhood, postal code, landmark, + or other point of interest. Note that this project's focus is on the + New York City metro area and surrounding locations, so no communities + will be found outside that extent. {children} - - My Location - - - - Search by your current location (your info will not be stored or - shared in any way). - - -
    - -
    ) } @@ -198,7 +164,7 @@ export const MapCtrlBtns: FC = (props) => { // TODO: confirm: // https://docs.mapbox.com/api/search/#data-types types="address,poi,postcode,locality,place,neighborhood" - placeholder="Search locations..." + placeholder="Enter a location..." containerRef={geocoderContainerRef} mapboxApiAccessToken={MAPBOX_TOKEN} proximity={NYC_LAT_LONG} From e5d610339ae245501b8f53e73545953da70944a6 Mon Sep 17 00:00:00 2001 From: Jason Lampel Date: Sun, 30 Aug 2020 14:57:00 -0600 Subject: [PATCH 12/18] MB config: add three more custom-font endo's --- public/data/mb-styles.langs.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/public/data/mb-styles.langs.json b/public/data/mb-styles.langs.json index f356933a..9dc0eb45 100644 --- a/public/data/mb-styles.langs.json +++ b/public/data/mb-styles.langs.json @@ -206,6 +206,12 @@ "case", ["==", ["var", "lang"], "Khmer"], ["literal", ["Noto Sans Khmer Regular"]], + ["==", ["var", "lang"], "Santali"], + ["literal", ["Noto Sans Ol Chiki Regular"]], + ["==", ["var", "lang"], "Sinhalese"], + ["literal", ["Noto Sans Sinhala Regular"]], + ["==", ["var", "lang"], "Meithei"], + ["literal", ["Noto Sans Meetei Mayek Regular"]], ["==", ["var", "lang"], "Balti"], ["literal", ["Noto Sans Tibetan Regular"]], ["==", ["var", "lang"], "Chocha-ngacha"], From 46aca9bbfe870e818062d3105686d7cb14c67993 Mon Sep 17 00:00:00 2001 From: Jason Lampel Date: Sun, 30 Aug 2020 20:27:30 -0600 Subject: [PATCH 13/18] Initial extent: zoom to calculated bounds after load --- src/components/map/Map.tsx | 65 +++++++++++++++++++++++------------- src/components/map/config.ts | 12 +++++++ 2 files changed, 53 insertions(+), 24 deletions(-) diff --git a/src/components/map/Map.tsx b/src/components/map/Map.tsx index 906ce62a..a0a43614 100644 --- a/src/components/map/Map.tsx +++ b/src/components/map/Map.tsx @@ -1,7 +1,11 @@ import React, { FC, useState, useContext, useEffect } from 'react' import { useHistory } from 'react-router-dom' import { AttributionControl, Map as MbMap, setRTLTextPlugin } from 'mapbox-gl' -import MapGL, { InteractiveMap, MapLoadEvent } from 'react-map-gl' +import MapGL, { + InteractiveMap, + MapLoadEvent, + WebMercatorViewport, +} from 'react-map-gl' import { useTheme } from '@material-ui/core/styles' import 'mapbox-gl/dist/mapbox-gl.css' @@ -13,8 +17,8 @@ import { MapPopup } from './MapPopup' import { MapTooltip } from './MapTooltip' import { MapCtrlBtns } from './MapCtrlBtns' import * as MapTypes from './types' -import * as mapUtils from './utils' -import * as mapConfig from './config' +import * as utils from './utils' +import * as config from './config' import { LangRecordSchema } from '../../context/types' import { getIDfromURLparams, @@ -22,7 +26,7 @@ import { useWindowResize, } from '../../utils' -const { layerId: sourceLayer, internalSrcID } = mapConfig.mbStyleTileConfig +const { layerId: sourceLayer, internalSrcID } = config.mbStyleTileConfig // Jest or whatever CANNOT find this plugin. And importing it from // `react-map-gl` is useless as well. @@ -52,7 +56,7 @@ export const Map: FC = ({ const theme = useTheme() const [isDesktop, setIsDesktop] = useState(false) const { width, height } = useWindowResize() - const [viewport, setViewport] = useState(mapConfig.initialMapState) + const [viewport, setViewport] = useState(config.initialMapState) const [mapOffset, setMapOffset] = useState<[number, number]>([0, 0]) const [popupOpen, setPopupOpen] = useState(null) const [tooltipOpen, setTooltipOpen] = useState( @@ -64,7 +68,7 @@ export const Map: FC = ({ useEffect((): void => { const deskBreakPoint = theme.breakpoints.values.md const wideFella = width >= deskBreakPoint - const offset = mapUtils.prepMapOffset(wideFella) + const offset = utils.prepMapOffset(wideFella) setIsDesktop(wideFella) setMapOffset(offset) @@ -79,7 +83,7 @@ export const Map: FC = ({ const map: MbMap = mapRef.current.getMap() const currentLayerNames = state.legendItems.map((item) => item.legendLabel) - mapUtils.filterLayersByFeatIDs(map, currentLayerNames, langFeatIDs) + utils.filterLayersByFeatIDs(map, currentLayerNames, langFeatIDs) }, [langFeatIDs]) // TODO: put in... legend? @@ -94,7 +98,7 @@ export const Map: FC = ({ useEffect((): void => { if (mapRef.current) { const map: MbMap = mapRef.current.getMap() - mapUtils.addLangTypeIconsToMap(map, mapConfig.langTypeIconsConfig) + utils.addLangTypeIconsToMap(map, config.langTypeIconsConfig) } }, [baselayer]) // baselayer may be irrelevant if only using Light BG @@ -122,7 +126,7 @@ export const Map: FC = ({ setPopupOpen(null) setSelFeatState(map, ID, true) - mapUtils.flyToCoords( + utils.flyToCoords( map, { latitude, longitude, zoom: 12 }, mapOffset, @@ -140,7 +144,7 @@ export const Map: FC = ({ } function onHover(event: MapTypes.MapEvent) { - mapUtils.handleHover(event, internalSrcID, setTooltipOpen) + utils.handleHover(event, internalSrcID, setTooltipOpen) } // Runs only once and kicks off the whole thinig @@ -205,13 +209,28 @@ export const Map: FC = ({ // NOTE: could not get this into the same `useEffect` that handles when // selFeatAttribs or mapLoaded are changed with an MB error/crash. if (!matchingRecord) { - const configKey = isDesktop ? 'desktop' : 'mobile' + const wmViewport = new WebMercatorViewport({ + width, + height, + }).fitBounds(config.initialBounds, { + padding: { + bottom: isDesktop ? mapOffset[1] : height / 2, + left: isDesktop ? mapOffset[0] : 0, + right: 0, + top: 0, + }, + }) - mapUtils.flyToCoords( - map, - { ...mapConfig.postLoadMapView[configKey] }, - mapOffset, - null + const { latitude, longitude, zoom } = wmViewport + + // Don't really need the `flyToCoords` util for this first one + map.flyTo( + { + essential: true, + center: { lng: longitude, lat: latitude }, + zoom, + }, + { selFeatAttribs, forceViewportUpdate: true } ) } @@ -241,16 +260,14 @@ export const Map: FC = ({ // the `id` in the URL if there is already a selected feature, which feels a // little weird, but it's much better than relying on a dummy route like // `id=-1`. Still not the greatest so keep an eye out for a better solution. - if (!mapUtils.areLangFeatsUnderCursor(event.features, internalSrcID)) { + if (!utils.areLangFeatsUnderCursor(event.features, internalSrcID)) { dispatch({ type: 'SET_SEL_FEAT_ATTRIBS', payload: null, }) - // TODO: decide /how/whether to force the panel open if nothing is found return } - setTooltipOpen(null) // super annoying if tooltip stays intact after a click setPopupOpen(null) @@ -285,10 +302,10 @@ export const Map: FC = ({ if (actionID === 'home') { const configKey = isDesktop ? 'desktop' : 'mobile' - mapUtils.flyToCoords( + utils.flyToCoords( map, { - ...mapConfig.postLoadMapView[configKey], + ...config.postLoadMapView[configKey], disregardCurrZoom: true, }, mapOffset, @@ -300,7 +317,7 @@ export const Map: FC = ({ const { zoom, latitude, longitude } = viewport - mapUtils.flyToCoords( + utils.flyToCoords( map, { latitude, @@ -326,8 +343,8 @@ export const Map: FC = ({ width="100%" attributionControl={false} mapOptions={{ logoPosition: 'bottom-right' }} - mapboxApiAccessToken={mapConfig.MAPBOX_TOKEN} - mapStyle={mapConfig.mbStyleTileConfig.customStyles.light} + mapboxApiAccessToken={config.MAPBOX_TOKEN} + mapStyle={config.mbStyleTileConfig.customStyles.light} onViewportChange={setViewport} onClick={onNativeClick} // TODO: mv into utils onHover={onHover} diff --git a/src/components/map/config.ts b/src/components/map/config.ts index 0004010a..6fd72501 100644 --- a/src/components/map/config.ts +++ b/src/components/map/config.ts @@ -34,12 +34,24 @@ export const NYC_LAT_LONG = { latitude: 40.7128, longitude: -74.006 } // Unsure why it needs the type here but not for feature coords.. const mapCenter = [-73.96, 40.7128] as [number, number] +// Ideally we'd just start from the bounds of the languages layer, because what +// happens is: +// 1) initial state used +// 2) zoom to extent of bounds to get all features into state +// 3) zoom to initialBounds export const initialMapState = { latitude: mapCenter[1], longitude: mapCenter[0], zoom: 5, } +// This is for #3 above. It should include the 5 boroughs and bits of NJ, and +// centered on Manhattan. +export const initialBounds = [ + [-74.19564, 40.574533], + [-73.767185, 40.892251], +] as [[number, number], [number, number]] + // After the `fitBounds` happens and gets all the features into state export const postLoadMapView = { desktop: { From 85169cd8476ecb39252ec993c9f5b7b9b69138fc Mon Sep 17 00:00:00 2001 From: Jason Lampel Date: Sun, 30 Aug 2020 20:54:26 -0600 Subject: [PATCH 14/18] Map: zoom home btn: zoom to initial bounds --- src/components/map/Map.tsx | 101 ++++++++++++----------------------- src/components/map/config.ts | 14 ----- src/components/map/utils.ts | 23 +++++++- 3 files changed, 56 insertions(+), 82 deletions(-) diff --git a/src/components/map/Map.tsx b/src/components/map/Map.tsx index a0a43614..4518b226 100644 --- a/src/components/map/Map.tsx +++ b/src/components/map/Map.tsx @@ -1,11 +1,7 @@ import React, { FC, useState, useContext, useEffect } from 'react' import { useHistory } from 'react-router-dom' import { AttributionControl, Map as MbMap, setRTLTextPlugin } from 'mapbox-gl' -import MapGL, { - InteractiveMap, - MapLoadEvent, - WebMercatorViewport, -} from 'react-map-gl' +import MapGL, { InteractiveMap, MapLoadEvent } from 'react-map-gl' import { useTheme } from '@material-ui/core/styles' import 'mapbox-gl/dist/mapbox-gl.css' @@ -76,9 +72,7 @@ export const Map: FC = ({ // TODO: another file useEffect((): void => { - if (!mapRef.current || !mapLoaded) { - return - } + if (!mapRef.current || !mapLoaded) return const map: MbMap = mapRef.current.getMap() const currentLayerNames = state.legendItems.map((item) => item.legendLabel) @@ -105,18 +99,14 @@ export const Map: FC = ({ // Do selected feature stuff on sel feat change or map load useEffect((): void => { // Map not ready - if (!mapRef.current || !mapLoaded) { - return - } + if (!mapRef.current || !mapLoaded) return const map: MbMap = mapRef.current.getMap() // Deselect all features clearAllSelFeats(map) - if (!selFeatAttribs) { - return - } + if (!selFeatAttribs) return // NOTE: won't get this far on load even if feature is selected. The timing // and order of the whole process prevent that. Make feature appear selected @@ -179,7 +169,10 @@ export const Map: FC = ({ // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore const langSrcBounds = map.getSource('languages-src').bounds - map.fitBounds(langSrcBounds) // ensure all feats are visible + + // Ensure all feats are visible. TODO: start from actual layer bounds + // somehow, then zoom is not needed. + map.fitBounds(langSrcBounds) const idFromUrl = getIDfromURLparams(window.location.search) const cacheOfIDs: number[] = [] @@ -208,53 +201,20 @@ export const Map: FC = ({ // NOTE: could not get this into the same `useEffect` that handles when // selFeatAttribs or mapLoaded are changed with an MB error/crash. - if (!matchingRecord) { - const wmViewport = new WebMercatorViewport({ - width, - height, - }).fitBounds(config.initialBounds, { - padding: { - bottom: isDesktop ? mapOffset[1] : height / 2, - left: isDesktop ? mapOffset[0] : 0, - right: 0, - top: 0, - }, - }) - - const { latitude, longitude, zoom } = wmViewport - - // Don't really need the `flyToCoords` util for this first one - map.flyTo( - { - essential: true, - center: { lng: longitude, lat: latitude }, - zoom, - }, - { selFeatAttribs, forceViewportUpdate: true } - ) - } + if (!matchingRecord) flyHome() // TODO: set paint property // https://docs.mapbox.com/mapbox-gl-js/api/map/#map#setpaintproperty - dispatch({ - type: 'INIT_LANG_LAYER_FEATURES', - payload: uniqueRecords, - }) - - dispatch({ - type: 'SET_MAP_LOADED', - payload: true, - }) + dispatch({ type: 'INIT_LANG_LAYER_FEATURES', payload: uniqueRecords }) + dispatch({ type: 'SET_MAP_LOADED', payload: true }) map.addControl(new AttributionControl({ compact: false }), 'bottom-right') } function onNativeClick(event: MapTypes.MapEvent): void { // Map not ready - if (!mapRef || !mapRef.current || !mapLoaded) { - return - } + if (!mapRef || !mapRef.current || !mapLoaded) return // No language features under click, clear the route. Note that this keeps // the `id` in the URL if there is already a selected feature, which feels a @@ -281,11 +241,30 @@ export const Map: FC = ({ map.removeFeatureState({ source: internalSrcID, sourceLayer }) } + function flyHome() { + if (!mapRef.current) return + + const map: MbMap = mapRef.current.getMap() + const { latitude, longitude, zoom } = utils.getWebMercSettings( + width, + height, + isDesktop, + mapOffset + ) + + // Don't really need the `flyToCoords` util for this first one + map.flyTo( + { + center: { lng: longitude, lat: latitude }, + zoom, + }, + { selFeatAttribs, forceViewportUpdate: true } + ) + } + // TODO: into utils if it doesn't require passing 1000 args function onMapCtrlClick(actionID: MapTypes.MapControlAction) { - if (!mapRef.current) { - return - } + if (!mapRef.current) return if (actionID === 'info') { dispatch({ @@ -300,17 +279,7 @@ export const Map: FC = ({ setPopupOpen(null) // otherwise janky lag while map is moving if (actionID === 'home') { - const configKey = isDesktop ? 'desktop' : 'mobile' - - utils.flyToCoords( - map, - { - ...config.postLoadMapView[configKey], - disregardCurrZoom: true, - }, - mapOffset, - selFeatAttribs - ) + flyHome() return // assumes `in` or `out` from here down } diff --git a/src/components/map/config.ts b/src/components/map/config.ts index 6fd72501..f150712a 100644 --- a/src/components/map/config.ts +++ b/src/components/map/config.ts @@ -52,20 +52,6 @@ export const initialBounds = [ [-73.767185, 40.892251], ] as [[number, number], [number, number]] -// After the `fitBounds` happens and gets all the features into state -export const postLoadMapView = { - desktop: { - latitude: 40.7186, - longitude: -73.9079, - zoom: 10.76, - }, - mobile: { - latitude: 40.7293, - longitude: -73.9059, - zoom: 9.39, - }, -} - export const langTypeIconsConfig = [ { icon: iconTree, id: '_tree' }, { icon: iconBook, id: '_book' }, diff --git a/src/components/map/utils.ts b/src/components/map/utils.ts index 76f73766..bbdec2d5 100644 --- a/src/components/map/utils.ts +++ b/src/components/map/utils.ts @@ -1,7 +1,9 @@ import { Map } from 'mapbox-gl' +import { WebMercatorViewport } from 'react-map-gl' import { PAGE_HEADER_ID } from 'components/nav/config' import * as MapTypes from './types' +import { initialBounds } from './config' import { isURL } from '../../utils' // One of the problems of using panels which overlap the map is how to deal with @@ -60,8 +62,6 @@ export const flyToCoords: MapTypes.FlyToCoords = ( map.flyTo( { - // Animation considered essential with respect to prefers-reduced-motion - essential: true, center: { lng, lat }, offset, zoom: zoomToUse, @@ -155,3 +155,22 @@ export const filterLayersByFeatIDs = ( } }) } + +export const getWebMercSettings = ( + width: number, + height: number, + isDesktop: boolean, + mapOffset: [number, number] +): { latitude: number; longitude: number; zoom: number } => { + return new WebMercatorViewport({ + width, + height, + }).fitBounds(initialBounds, { + padding: { + bottom: isDesktop ? mapOffset[1] : height / 2, + left: isDesktop ? mapOffset[0] : 0, + right: 0, + top: 0, + }, + }) +} From 18cd9a91aefe66aff1e774710ed0e5fa1779e683 Mon Sep 17 00:00:00 2001 From: Jason Lampel Date: Sun, 30 Aug 2020 21:11:11 -0600 Subject: [PATCH 15/18] Geocoder: componentize --- src/components/map/GeocoderPopout.tsx | 96 +++++++++++++++++++++++++++ src/components/map/MapCtrlBtns.tsx | 58 ++-------------- 2 files changed, 100 insertions(+), 54 deletions(-) create mode 100644 src/components/map/GeocoderPopout.tsx diff --git a/src/components/map/GeocoderPopout.tsx b/src/components/map/GeocoderPopout.tsx new file mode 100644 index 00000000..8b67a4a2 --- /dev/null +++ b/src/components/map/GeocoderPopout.tsx @@ -0,0 +1,96 @@ +import React, { FC } from 'react' +import { makeStyles, createStyles, Theme } from '@material-ui/core/styles' +import { Typography, Popover, Box } from '@material-ui/core' +import Geocoder from 'react-map-gl-geocoder' + +import 'react-map-gl-geocoder/dist/mapbox-gl-geocoder.css' + +import { flyToCoords } from './utils' +import { MapCtrlBtnsProps, GeocodeResult } from './types' +import { MAPBOX_TOKEN, NYC_LAT_LONG } from './config' + +const useStyles = makeStyles((theme: Theme) => + createStyles({ + popoverContent: { padding: '1em', width: 300 }, + popoverContentText: { marginBottom: '.75em', fontSize: '0.8em' }, + layersMenuPaper: { overflow: 'visible' }, + }) +) + +const LocationSearchContent: FC = (props) => { + const { children } = props + const classes = useStyles() + + return ( + + + Search by location + + + + Enter an address, municipality, neighborhood, postal code, landmark, + or other point of interest. Note that this project's focus is on the + New York City metro area and surrounding locations, so no communities + will be found outside that extent. + + + {children} + + ) +} + +type GeocoderProps = Pick & { + anchorEl: null | HTMLElement + setAnchorEl: React.Dispatch +} + +export const GeocoderPopout: FC = (props) => { + const { anchorEl, setAnchorEl, mapOffset, mapRef } = props + const classes = useStyles() + const layersMenuOpen = Boolean(anchorEl) + const geocoderContainerRef = React.useRef(null) + + const handleLayersMenuClose = () => setAnchorEl(null) + + const handleGeocodeResult = (geocodeResult: GeocodeResult) => { + handleLayersMenuClose() + + if (mapRef.current) { + flyToCoords( + mapRef.current.getMap(), + { + latitude: geocodeResult.result.center[1], + longitude: geocodeResult.result.center[0], + zoom: 15, // TODO: bounds + }, + mapOffset, + null + ) + } + } + + return ( + + +
    + + + + ) +} diff --git a/src/components/map/MapCtrlBtns.tsx b/src/components/map/MapCtrlBtns.tsx index 867bc594..f7de1f0f 100644 --- a/src/components/map/MapCtrlBtns.tsx +++ b/src/components/map/MapCtrlBtns.tsx @@ -1,7 +1,6 @@ import React, { FC } from 'react' import { makeStyles, createStyles, Theme } from '@material-ui/core/styles' -import { Typography, Popover, Box } from '@material-ui/core' -import Geocoder from 'react-map-gl-geocoder' +import { Typography, Box } from '@material-ui/core' import { SpeedDial, SpeedDialIcon, @@ -14,9 +13,8 @@ import { FiHome, FiZoomIn, FiZoomOut, FiInfo } from 'react-icons/fi' import 'react-map-gl-geocoder/dist/mapbox-gl-geocoder.css' -import { flyToCoords } from './utils' -import { MapCtrlBtnsProps, CtrlBtnConfig, GeocodeResult } from './types' -import { MAPBOX_TOKEN, NYC_LAT_LONG } from './config' +import { MapCtrlBtnsProps, CtrlBtnConfig } from './types' +import { GeocoderPopout } from './GeocoderPopout' const useStyles = makeStyles((theme: Theme) => createStyles({ @@ -103,29 +101,9 @@ export const MapCtrlBtns: FC = (props) => { const classes = useStyles() const [speedDialOpen, setSpeedDialOpen] = React.useState(true) const size = isDesktop ? 'medium' : 'small' - const geocoderContainerRef = React.useRef(null) const [anchorEl, setAnchorEl] = React.useState(null) - const layersMenuOpen = Boolean(anchorEl) const handleSpeedDialRootClick = () => setSpeedDialOpen(!speedDialOpen) - const handleLayersMenuClose = () => setAnchorEl(null) - - const handleGeocodeResult = (geocodeResult: GeocodeResult) => { - handleLayersMenuClose() - - if (mapRef.current) { - flyToCoords( - mapRef.current.getMap(), - { - latitude: geocodeResult.result.center[1], - longitude: geocodeResult.result.center[0], - zoom: 13, - }, - mapOffset, - null - ) - } - } const handleClose = ( e: React.SyntheticEvent, Event>, @@ -143,35 +121,7 @@ export const MapCtrlBtns: FC = (props) => { return ( <> - - -
    - - - + Date: Sun, 30 Aug 2020 22:20:50 -0600 Subject: [PATCH 16/18] Geocoder: zoom to polygon extent; cleanup --- src/components/map/GeocoderPopout.tsx | 47 ++++++++++++++++++++------- src/components/map/Map.tsx | 23 +++++-------- src/components/map/MapCtrlBtns.tsx | 45 ++----------------------- src/components/map/config.ts | 3 +- src/components/map/types.ts | 10 ++++-- src/components/map/utils.ts | 17 ++++------ 6 files changed, 63 insertions(+), 82 deletions(-) diff --git a/src/components/map/GeocoderPopout.tsx b/src/components/map/GeocoderPopout.tsx index 8b67a4a2..9b41f7b2 100644 --- a/src/components/map/GeocoderPopout.tsx +++ b/src/components/map/GeocoderPopout.tsx @@ -1,13 +1,15 @@ import React, { FC } from 'react' +import { Map } from 'mapbox-gl' import { makeStyles, createStyles, Theme } from '@material-ui/core/styles' import { Typography, Popover, Box } from '@material-ui/core' import Geocoder from 'react-map-gl-geocoder' import 'react-map-gl-geocoder/dist/mapbox-gl-geocoder.css' -import { flyToCoords } from './utils' +import { flyToCoords, getWebMercSettings } from './utils' import { MapCtrlBtnsProps, GeocodeResult } from './types' import { MAPBOX_TOKEN, NYC_LAT_LONG } from './config' +import { useWindowResize } from '../../utils' const useStyles = makeStyles((theme: Theme) => createStyles({ @@ -39,32 +41,55 @@ const LocationSearchContent: FC = (props) => { ) } -type GeocoderProps = Pick & { +type GeocoderProps = Omit & { anchorEl: null | HTMLElement setAnchorEl: React.Dispatch } export const GeocoderPopout: FC = (props) => { - const { anchorEl, setAnchorEl, mapOffset, mapRef } = props + const { anchorEl, setAnchorEl, mapOffset, mapRef, isDesktop } = props const classes = useStyles() const layersMenuOpen = Boolean(anchorEl) const geocoderContainerRef = React.useRef(null) + const { width, height } = useWindowResize() const handleLayersMenuClose = () => setAnchorEl(null) const handleGeocodeResult = (geocodeResult: GeocodeResult) => { handleLayersMenuClose() - if (mapRef.current) { - flyToCoords( - mapRef.current.getMap(), + if (!mapRef.current) return + + const { center, bbox } = geocodeResult.result + + if (bbox) { + const { latitude, longitude, zoom } = getWebMercSettings( + width, + height, + isDesktop, + mapOffset, + [ + [bbox[0], bbox[1]], + [bbox[2], bbox[3]], + ] + ) + + const map: Map = mapRef.current.getMap() + + map.flyTo( { - latitude: geocodeResult.result.center[1], - longitude: geocodeResult.result.center[0], - zoom: 15, // TODO: bounds + // Not THAT essential if you... don't like cool things + essential: true, + center: { lng: longitude, lat: latitude }, + zoom, }, - mapOffset, - null + { forceViewportUpdate: true } + ) + } else { + flyToCoords( + mapRef.current.getMap(), + { latitude: center[1], longitude: center[0], zoom: 15 }, + mapOffset ) } } diff --git a/src/components/map/Map.tsx b/src/components/map/Map.tsx index 4518b226..66c09438 100644 --- a/src/components/map/Map.tsx +++ b/src/components/map/Map.tsx @@ -116,16 +116,11 @@ export const Map: FC = ({ setPopupOpen(null) setSelFeatState(map, ID, true) - utils.flyToCoords( - map, - { latitude, longitude, zoom: 12 }, - mapOffset, - selFeatAttribs - ) + utils.flyToCoords(map, { latitude, longitude, zoom: 12 }, mapOffset) }, [selFeatAttribs, mapLoaded]) /* eslint-enable react-hooks/exhaustive-deps */ - // TODO: animate selected feature + // TODO: higher zIndex on selected feature function setSelFeatState(map: MbMap, id: number, selected: boolean) { map.setFeatureState( { sourceLayer, source: internalSrcID, id }, @@ -157,7 +152,7 @@ export const Map: FC = ({ }) map.on('zoomend', function onMoveEnd(zoomEndEvent) { - if (zoomEndEvent.selFeatAttribs && !map.isMoving()) { + if (selFeatAttribs && !map.isMoving()) { setPopupOpen({ latitude: zoomEndEvent.selFeatAttribs.Latitude, longitude: zoomEndEvent.selFeatAttribs.Longitude, @@ -249,12 +244,14 @@ export const Map: FC = ({ width, height, isDesktop, - mapOffset + mapOffset, + config.initialBounds ) // Don't really need the `flyToCoords` util for this first one map.flyTo( { + essential: true, // not THAT essential if you... don't like cool things center: { lng: longitude, lat: latitude }, zoom, }, @@ -284,18 +281,16 @@ export const Map: FC = ({ return // assumes `in` or `out` from here down } - const { zoom, latitude, longitude } = viewport + const { zoom } = viewport utils.flyToCoords( map, { - latitude, - longitude, + ...viewport, zoom: actionID === 'in' ? zoom + 1 : zoom - 1, disregardCurrZoom: true, }, - [0, 0], - selFeatAttribs + [0, 0] ) } diff --git a/src/components/map/MapCtrlBtns.tsx b/src/components/map/MapCtrlBtns.tsx index f7de1f0f..a2c49e7f 100644 --- a/src/components/map/MapCtrlBtns.tsx +++ b/src/components/map/MapCtrlBtns.tsx @@ -1,6 +1,5 @@ import React, { FC } from 'react' import { makeStyles, createStyles, Theme } from '@material-ui/core/styles' -import { Typography, Box } from '@material-ui/core' import { SpeedDial, SpeedDialIcon, @@ -40,24 +39,6 @@ const useStyles = makeStyles((theme: Theme) => }, }, }, - popoverContent: { - padding: '1em', - width: 300, - }, - popoverContentHeading: { - marginTop: '.5rem', - }, - popoverContentText: { - marginBottom: '.75em', - fontSize: '0.8em', - }, - layersMenuPaper: { - overflow: 'visible', - }, - locationBtnWrap: { - display: 'flex', - justifyContent: 'center', - }, }) ) @@ -74,28 +55,6 @@ const ctrlBtnsConfig = [ { id: 'info', icon: , name: 'About & Info' }, ] as CtrlBtnConfig[] -export const LocationSearchContent: FC = (props) => { - const { children } = props - const classes = useStyles() - - return ( - - - Search by location - - - - Enter an address, municipality, neighborhood, postal code, landmark, - or other point of interest. Note that this project's focus is on the - New York City metro area and surrounding locations, so no communities - will be found outside that extent. - - - {children} - - ) -} - export const MapCtrlBtns: FC = (props) => { const { isDesktop, onMapCtrlClick, mapRef, mapOffset } = props const classes = useStyles() @@ -121,7 +80,9 @@ export const MapCtrlBtns: FC = (props) => { return ( <> - + void export type UseStyleProps = { @@ -119,7 +118,10 @@ export type UseStyleProps = { } export type GeocodeResult = { - result: { center: [number, number] } + result: { + center: [number, number] + bbox?: [number, number, number, number] + } } export type MapCtrlBtnsProps = { @@ -135,3 +137,5 @@ export type CtrlBtnConfig = { name: string customFn?: boolean } + +export type BoundsArray = [[number, number], [number, number]] diff --git a/src/components/map/utils.ts b/src/components/map/utils.ts index bbdec2d5..287e3a4c 100644 --- a/src/components/map/utils.ts +++ b/src/components/map/utils.ts @@ -3,7 +3,6 @@ import { WebMercatorViewport } from 'react-map-gl' import { PAGE_HEADER_ID } from 'components/nav/config' import * as MapTypes from './types' -import { initialBounds } from './config' import { isURL } from '../../utils' // One of the problems of using panels which overlap the map is how to deal with @@ -35,12 +34,7 @@ export const prepMapOffset = ( return [(sidePanelWidth + sidePanelGutter) / 2, topBarHeight / 2] } -export const flyToCoords: MapTypes.FlyToCoords = ( - map, - settings, - offset, - selFeatAttribs -) => { +export const flyToCoords: MapTypes.FlyToCoords = (map, settings, offset) => { const { zoom: targetZoom, latitude: lat, @@ -49,9 +43,8 @@ export const flyToCoords: MapTypes.FlyToCoords = ( } = settings const currentZoom = map.getZoom() const customEventData = { - forceViewportUpdate: true, // to keep state in sync - selFeatAttribs, // popup data disregardCurrZoom, + forceViewportUpdate: true, // to keep state in sync } let zoomToUse = targetZoom @@ -62,6 +55,7 @@ export const flyToCoords: MapTypes.FlyToCoords = ( map.flyTo( { + essential: true, // not THAT essential if you... don't like cool things center: { lng, lat }, offset, zoom: zoomToUse, @@ -160,12 +154,13 @@ export const getWebMercSettings = ( width: number, height: number, isDesktop: boolean, - mapOffset: [number, number] + mapOffset: [number, number], + bounds: MapTypes.BoundsArray ): { latitude: number; longitude: number; zoom: number } => { return new WebMercatorViewport({ width, height, - }).fitBounds(initialBounds, { + }).fitBounds(bounds, { padding: { bottom: isDesktop ? mapOffset[1] : height / 2, left: isDesktop ? mapOffset[0] : 0, From cd6ca1438763e897a014bd3bc6b9ef2e13706287 Mon Sep 17 00:00:00 2001 From: Jason Lampel Date: Mon, 31 Aug 2020 10:24:08 -0600 Subject: [PATCH 17/18] Details and geocoder popout: minor wording improvements --- src/components/details/DetailsPanel.tsx | 6 +++--- src/components/map/GeocoderPopout.tsx | 4 +--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/components/details/DetailsPanel.tsx b/src/components/details/DetailsPanel.tsx index c8b93529..6757e99d 100644 --- a/src/components/details/DetailsPanel.tsx +++ b/src/components/details/DetailsPanel.tsx @@ -79,8 +79,7 @@ const RandomLink: FC = () => { return ( <> - , or{' '} - try a random community + , or try one at random ) } @@ -88,7 +87,7 @@ const RandomLink: FC = () => { const NoFeatSel: FC = () => { return ( - No community selected. Click a point in the map or a row in the data table + No community selected. Click a community in the map, in the data table . ) @@ -142,6 +141,7 @@ export const DetailsPanel: FC = () => { {Language} )} + {/* TODO: make "+4 more clickable to toggle popover" */} {Neighborhoods ? prettyTruncateList(Neighborhoods) : Town} diff --git a/src/components/map/GeocoderPopout.tsx b/src/components/map/GeocoderPopout.tsx index 9b41f7b2..eb4c9262 100644 --- a/src/components/map/GeocoderPopout.tsx +++ b/src/components/map/GeocoderPopout.tsx @@ -31,9 +31,7 @@ const LocationSearchContent: FC = (props) => { Enter an address, municipality, neighborhood, postal code, landmark, - or other point of interest. Note that this project's focus is on the - New York City metro area and surrounding locations, so no communities - will be found outside that extent. + or other point of interest within the New York City metro area. {children} From efc8b8e7888b084019a0fb7e9d144ec023c5269f Mon Sep 17 00:00:00 2001 From: Jason Lampel Date: Mon, 31 Aug 2020 10:26:49 -0600 Subject: [PATCH 18/18] Map popups: restore (broken by a2c9a93), consistentize behavior for geocoder: Close the popup on `movestart` in all instances including geocoder search to avoid the janky feel of popup staying open while moving. --- src/components/map/GeocoderPopout.tsx | 13 +++- src/components/map/Map.tsx | 107 ++++++++++++++------------ src/components/map/types.ts | 3 +- src/components/map/utils.ts | 10 ++- 4 files changed, 76 insertions(+), 57 deletions(-) diff --git a/src/components/map/GeocoderPopout.tsx b/src/components/map/GeocoderPopout.tsx index eb4c9262..ac02a660 100644 --- a/src/components/map/GeocoderPopout.tsx +++ b/src/components/map/GeocoderPopout.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react' +import React, { FC, useContext } from 'react' import { Map } from 'mapbox-gl' import { makeStyles, createStyles, Theme } from '@material-ui/core/styles' import { Typography, Popover, Box } from '@material-ui/core' @@ -6,6 +6,7 @@ import Geocoder from 'react-map-gl-geocoder' import 'react-map-gl-geocoder/dist/mapbox-gl-geocoder.css' +import { GlobalContext } from 'components' import { flyToCoords, getWebMercSettings } from './utils' import { MapCtrlBtnsProps, GeocodeResult } from './types' import { MAPBOX_TOKEN, NYC_LAT_LONG } from './config' @@ -45,6 +46,7 @@ type GeocoderProps = Omit & { } export const GeocoderPopout: FC = (props) => { + const { state } = useContext(GlobalContext) const { anchorEl, setAnchorEl, mapOffset, mapRef, isDesktop } = props const classes = useStyles() const layersMenuOpen = Boolean(anchorEl) @@ -81,13 +83,18 @@ export const GeocoderPopout: FC = (props) => { center: { lng: longitude, lat: latitude }, zoom, }, - { forceViewportUpdate: true } + { + forceViewportUpdate: true, + selFeatAttribs: state.selFeatAttribs, + fromPoly: true, + } ) } else { flyToCoords( mapRef.current.getMap(), { latitude: center[1], longitude: center[0], zoom: 15 }, - mapOffset + mapOffset, + state.selFeatAttribs ) } } diff --git a/src/components/map/Map.tsx b/src/components/map/Map.tsx index 66c09438..c595ff11 100644 --- a/src/components/map/Map.tsx +++ b/src/components/map/Map.tsx @@ -113,10 +113,14 @@ export const Map: FC = ({ const { ID, Latitude: latitude, Longitude: longitude } = selFeatAttribs - setPopupOpen(null) setSelFeatState(map, ID, true) - utils.flyToCoords(map, { latitude, longitude, zoom: 12 }, mapOffset) + utils.flyToCoords( + map, + { latitude, longitude, zoom: 12 }, + mapOffset, + selFeatAttribs + ) }, [selFeatAttribs, mapLoaded]) /* eslint-enable react-hooks/exhaustive-deps */ @@ -136,31 +140,6 @@ export const Map: FC = ({ function onLoad(mapLoadEvent: MapLoadEvent) { const { target: map } = mapLoadEvent - // Maintain viewport state sync if needed (e.g. after things like `flyTo`), - // otherwise the map shifts back to previous position after panning or - // zooming. - map.on('moveend', function onMoveEnd(zoomEndEvent) { - // No custom event data, regular move event - if (zoomEndEvent.forceViewportUpdate) { - setViewport({ - ...viewport, // spreading just in case bearing or pitch are added - zoom: map.getZoom(), - latitude: map.getCenter().lat, - longitude: map.getCenter().lng, - }) - } - }) - - map.on('zoomend', function onMoveEnd(zoomEndEvent) { - if (selFeatAttribs && !map.isMoving()) { - setPopupOpen({ - latitude: zoomEndEvent.selFeatAttribs.Latitude, - longitude: zoomEndEvent.selFeatAttribs.Longitude, - selFeatAttribs: zoomEndEvent.selFeatAttribs, - }) - } - }) - // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore const langSrcBounds = map.getSource('languages-src').bounds @@ -173,7 +152,6 @@ export const Map: FC = ({ const cacheOfIDs: number[] = [] const uniqueRecords: LangRecordSchema[] = [] const rawLangFeats = map.querySourceFeatures(internalSrcID, { sourceLayer }) - // Just the properties for table/results, don't need GeoJSON cruft. Also // need to make sure each ID is unique as there have been initial data // inconsistencies, and more importantly MB may have feature duplication if @@ -205,6 +183,38 @@ export const Map: FC = ({ dispatch({ type: 'SET_MAP_LOADED', payload: true }) map.addControl(new AttributionControl({ compact: false }), 'bottom-right') + + // Maintain viewport state sync if needed (e.g. after things like `flyTo`), + // otherwise the map shifts back to previous position after panning or + // zooming. + map.on('moveend', function onMoveEnd(zoomEndEvent) { + // No custom event data, regular move event + if (zoomEndEvent.forceViewportUpdate) { + setViewport({ + ...viewport, // spreading just in case bearing or pitch are added + zoom: map.getZoom(), + latitude: map.getCenter().lat, + longitude: map.getCenter().lng, + }) + } + }) + + // Close popup on the start of moving so no jank + map.on('movestart', function onMoveStart(zoomEndEvent) { + if (zoomEndEvent.selFeatAttribs) setPopupOpen(null) + }) + + map.on('zoomend', function onMoveEnd(zoomEndEvent) { + const { selFeatAttribs: attribs } = zoomEndEvent + + if (attribs && !map.isMoving()) { + setPopupOpen({ + latitude: attribs.Latitude, + longitude: attribs.Longitude, + selFeatAttribs: attribs, + }) + } + }) } function onNativeClick(event: MapTypes.MapEvent): void { @@ -223,8 +233,9 @@ export const Map: FC = ({ return } + setTooltipOpen(null) // super annoying if tooltip stays intact after a click - setPopupOpen(null) + setPopupOpen(null) // closed by movestate anyway, but smoother this way // TODO: use `initialEntries` in to test routing history.push(`/details?id=${event.features[0].properties.ID}`) @@ -267,31 +278,25 @@ export const Map: FC = ({ dispatch({ type: 'TOGGLE_OFF_CANVAS_NAV', }) - - return - } - - const map: MbMap = mapRef.current.getMap() - - setPopupOpen(null) // otherwise janky lag while map is moving - - if (actionID === 'home') { + } else if (actionID === 'home') { flyHome() + } else { + // Assumes `in` or `out` from here down... - return // assumes `in` or `out` from here down + const map: MbMap = mapRef.current.getMap() + const { zoom } = viewport + + utils.flyToCoords( + map, + { + ...viewport, + zoom: actionID === 'in' ? zoom + 1 : zoom - 1, + disregardCurrZoom: true, + }, + [0, 0], + selFeatAttribs + ) } - - const { zoom } = viewport - - utils.flyToCoords( - map, - { - ...viewport, - zoom: actionID === 'in' ? zoom + 1 : zoom - 1, - disregardCurrZoom: true, - }, - [0, 0] - ) } return ( diff --git a/src/components/map/types.ts b/src/components/map/types.ts index cc4f6284..c37001e2 100644 --- a/src/components/map/types.ts +++ b/src/components/map/types.ts @@ -109,7 +109,8 @@ export type FlyToCoords = ( zoom?: number | 10.25 disregardCurrZoom?: boolean // e.g. when using map controls } & LongLat, - offset: [number, number] + offset: [number, number], + selFeatAttribs: LangRecordSchema | null ) => void export type UseStyleProps = { diff --git a/src/components/map/utils.ts b/src/components/map/utils.ts index 287e3a4c..3b13e0a5 100644 --- a/src/components/map/utils.ts +++ b/src/components/map/utils.ts @@ -34,7 +34,12 @@ export const prepMapOffset = ( return [(sidePanelWidth + sidePanelGutter) / 2, topBarHeight / 2] } -export const flyToCoords: MapTypes.FlyToCoords = (map, settings, offset) => { +export const flyToCoords: MapTypes.FlyToCoords = ( + map, + settings, + offset, + selFeatAttribs +) => { const { zoom: targetZoom, latitude: lat, @@ -43,8 +48,9 @@ export const flyToCoords: MapTypes.FlyToCoords = (map, settings, offset) => { } = settings const currentZoom = map.getZoom() const customEventData = { - disregardCurrZoom, forceViewportUpdate: true, // to keep state in sync + selFeatAttribs, // popup data + disregardCurrZoom, } let zoomToUse = targetZoom