diff --git a/src/components/ToiletDetailsPanel.tsx b/src/components/ToiletDetailsPanel.tsx index 7bc4e3ad8..2da8a8a48 100644 --- a/src/components/ToiletDetailsPanel.tsx +++ b/src/components/ToiletDetailsPanel.tsx @@ -26,7 +26,6 @@ import parseISO from 'date-fns/parseISO'; import add from 'date-fns/add'; import Link from 'next/link'; import useComponentSize from '@rehooks/component-size'; -import L from 'leaflet'; import Box from './Box'; import Button from './Button'; @@ -105,10 +104,11 @@ function round(value: number, precision = 0) { } const DistanceTo = ({ from, to }) => { - const fromLatLng = L.latLng(from.lat, from.lng); + // const fromLatLng = L.latLng(from.lat, from.lng); - const toLatLng = L.latLng(to.lat, to.lng); - const metersToLoo = fromLatLng.distanceTo(toLatLng); + // const toLatLng = L.latLng(to.lat, to.lng); + // const metersToLoo = fromLatLng.distanceTo(toLatLng); + const metersToLoo = 10000; const distance = metersToLoo < 1000 @@ -117,7 +117,7 @@ const DistanceTo = ({ from, to }) => { return ( - {distance} + FIXME {distance} ); }; diff --git a/src/components/useFilters.tsx b/src/components/useFilters.tsx new file mode 100644 index 000000000..0a4a5d9d5 --- /dev/null +++ b/src/components/useFilters.tsx @@ -0,0 +1,44 @@ +import { useEffect, useState, useMemo } from 'react'; +import config, { FILTERS_KEY } from '../config'; + +const useFilters = (toilets) => { + let initialState = config.getSettings(FILTERS_KEY); + // default any unsaved filters as 'false' + config.filters.forEach((filter) => { + initialState[filter.id] = initialState[filter.id] || false; + }); + const [filters, setFilters] = useState(initialState); + + // keep local storage and state in sync + useEffect(() => { + window.localStorage.setItem(FILTERS_KEY, JSON.stringify(filters)); + }, [filters]); + + // get the filter objects from config for the filters applied by the user + const applied = config.filters.filter((filter) => filters[filter.id]); + + const filtered = useMemo(() => { + if (toilets) { + return toilets.filter((toilet: { [x: string]: any }) => + applied.every((filter) => { + const value = toilet[filter.id]; + + if (value === null) { + return false; + } + + return !!value; + }) + ); + } + return []; + }, [toilets, applied]); + + return { + filtered, + filters, + setFilters, + }; +}; + +export default useFilters; diff --git a/src/pages/MapPage.tsx b/src/pages/MapPage.tsx deleted file mode 100644 index 1edd7858f..000000000 --- a/src/pages/MapPage.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { - GetServerSideProps, - GetServerSidePropsContext, - GetStaticProps, - GetStaticPropsContext, - InferGetServerSidePropsType, -} from 'next'; -import { useRouter } from 'next/router'; -import React from 'react'; - -import HomePage from '.'; - -const MapPage = ({ - lat, - lng, -}): InferGetServerSidePropsType => { - return ( - - ); -}; - -export const getServerSideProps: GetServerSideProps = async ( - context: GetServerSidePropsContext -) => { - return { - props: { - lat: context?.query['lat'], - lng: context?.query['lng'], - }, - }; -}; - -export default MapPage; diff --git a/src/pages/explorer/voyager.tsx b/src/pages/explorer/voyager.tsx index 467ea50dc..9aaae6e97 100644 --- a/src/pages/explorer/voyager.tsx +++ b/src/pages/explorer/voyager.tsx @@ -6,31 +6,31 @@ import config from '../../config'; import 'graphql-voyager/dist/voyager.css'; const VoyagerComponent = dynamic( - () => import('graphql-voyager').then((mod) => mod.Voyager), - { ssr: false } + () => import('graphql-voyager').then((mod) => mod.Voyager), + { ssr: false } ); function introspectionProvider(query) { - return fetch('/api', { - method: 'post', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ query: query }), - }).then((response) => response.json()); - } + return fetch('/api', { + method: 'post', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ query: query }), + }).then((response) => response.json()); +} - const Voyager = () => ( - - - {config.getTitle('Schema Visualisation')} - +const Voyager = () => ( + + + {config.getTitle('Schema Visualisation')} + - - - - - ); + + + + +); - export default Voyager; +export default Voyager; diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 1316d3f52..06dd663ee 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -1,5 +1,4 @@ -import React, { useState } from 'react'; -import PropTypes from 'prop-types'; +import React from 'react'; import Head from 'next/head'; import dynamic from 'next/dynamic'; import PageLayout from '../components/PageLayout'; @@ -8,160 +7,36 @@ import Sidebar from '../components/Sidebar'; import Notification from '../components/Notification'; import VisuallyHidden from '../components/VisuallyHidden'; import { useMapState } from '../components/MapState'; -import config, { FILTERS_KEY } from '../config'; +import config from '../config'; import { useRouter } from 'next/router'; import { withApollo } from '../components/withApollo'; import { NextPage } from 'next'; -import { - getServerPageFindLooById, - getServerPageFindLoosNearby, - useFindLooById, - useFindLoosNearby, -} from '../api-client/page'; - -/** - * SSR Migration plan - * --- - * - * Look at getStaticProps to fetch loos for lat/lng at build time. - * Look at using getStaticProps to pre-fetch /loos/[id]. - * - * Use ISR (https://nextjs.org/docs/basic-features/data-fetching#incremental-static-regeneration) - * ISR lets us regenerate loo pages and lat/lng loo list incrementally upon new requests - * Set revalidate to throttle this by n seconds. - */ +import { useFindLoosNearbyQuery } from '../api-client/graphql'; +import useFilters from '../components/useFilters'; const SIDEBAR_BOTTOM_MARGIN = 32; const MapLoader = () =>

Loading map...

; +const LooMap = dynamic(() => import('../components/LooMap'), { + loading: MapLoader, + ssr: false, +}); -const HomePage = ({ initialPosition, ...props }) => { +const HomePage = () => { + const router = useRouter(); + const { message } = router.query; const [mapState, setMapState] = useMapState(); - const LooMap = React.useMemo( - () => - dynamic(() => import('../components/LooMap'), { - loading: MapLoader, - ssr: false, - }), - [] - ); - const ToiletDetailsPanel = React.useMemo( - () => - dynamic(() => import('../components/ToiletDetailsPanel'), { - loading: MapLoader, - ssr: false, - }), - [] - ); - let initialState = config.getSettings(FILTERS_KEY); - // default any unsaved filters as 'false' - config.filters.forEach((filter) => { - initialState[filter.id] = initialState[filter.id] || false; + const { data } = useFindLoosNearbyQuery({ + variables: { + lat: mapState.center.lat, + lng: mapState.center.lng, + radius: Math.ceil(mapState.radius), + }, }); - const [filters, setFilters] = useState(initialState); - - // keep local storage and state in sync - React.useEffect(() => { - window.localStorage.setItem(FILTERS_KEY, JSON.stringify(filters)); - }, [filters]); - - const [toiletData, setToiletData] = React.useState([]); - - const router = useRouter(); - - /** - * Fetch nearby loo data when the map state changes. - * TODO: Try initial fetch using SSR. - */ - React.useEffect(() => { - async function fetchNearbyLooData() { - const variables = { - lat: mapState.center.lat, - lng: mapState.center.lng, - radius: Math.ceil(mapState.radius), - }; - const { props: toiletProps } = await getServerPageFindLoosNearby({ - variables, - }); - if (toiletProps.data !== undefined) { - setToiletData(toiletProps.data.loosByProximity); - } - } - fetchNearbyLooData(); - }, [mapState]); - - const { id: selectedLooId } = router.query; - - /** - * TODO: Fetch loo information as one big blob using SSR. - */ - const [selectedLoo, setSelectedLoo] = React.useState(null); - const [loadingSelectedLoo, setLoadingSelectedLoo] = React.useState(false); - React.useEffect(() => { - async function fetchNearbyLooData() { - const { props: toiletProps } = await getServerPageFindLooById({ - variables: { id: selectedLooId as string }, - }); - if (toiletProps.data !== undefined) { - setSelectedLoo(toiletProps.data.loo); - setLoadingSelectedLoo(false); - } - } - setLoadingSelectedLoo(true); - fetchNearbyLooData(); - }, [selectedLooId]); - - // const { message } = queryString.parse(props.location.search); - const message = 'TODO'; - - // get the filter objects from config for the filters applied by the user - const applied = config.filters.filter((filter) => filters[filter.id]); + const { filters, filtered, setFilters } = useFilters(data?.loosByProximity); - // restrict the results to only those toilets which pass all of our filter requirements - const toilets = toiletData.filter((toilet: { [x: string]: any }) => - applied.every((filter) => { - const value = toilet[filter.id]; - - if (value === null) { - return false; - } - - return !!value; - }) - ); - - const isLooPage = router.pathname === '/loos/[id]'; - const [shouldCenter, setShouldCenter] = React.useState(isLooPage); - - // set initial map center to toilet if on /loos/:id - React.useEffect(() => { - if (shouldCenter && selectedLoo) { - setMapState({ - center: selectedLoo.location, - }); - - // don't recenter the map each time the id changes - setShouldCenter(false); - } - }, [selectedLoo, shouldCenter, setMapState]); - - // set the map position if initialPosition prop exists - React.useEffect(() => { - if (initialPosition) { - setMapState({ - center: initialPosition, - }); - } - }, [initialPosition, setMapState]); - - const [toiletPanelDimensions, setToiletPanelDimensions] = React.useState({}); - - const pageTitle = config.getTitle( - isLooPage && selectedLoo - ? selectedLoo.name || 'Unnamed Toilet' - : 'Find Toilet' - ); + const pageTitle = config.getTitle('Home'); return ( @@ -182,9 +57,7 @@ const HomePage = ({ initialPosition, ...props }) => { right={0} m={3} maxWidth={326} - maxHeight={`calc(100% - ${ - toiletPanelDimensions.height || 0 - }px - ${SIDEBAR_BOTTOM_MARGIN}px)`} + maxHeight={`calc(100% - 0px - ${SIDEBAR_BOTTOM_MARGIN}px)`} overflowY="auto" // center on small viewports mx={['auto', 0]} @@ -199,66 +72,15 @@ const HomePage = ({ initialPosition, ...props }) => { { - if (toilet.id === selectedLooId) { - return { - ...toilet, - isHighlighted: true, - }; - } - return toilet; - })} + loos={filtered} center={mapState.center} zoom={mapState.zoom} onViewportChanged={setMapState} - controlsOffset={toiletPanelDimensions.height} + controlsOffset={0} /> - - {Boolean(selectedLooId) && selectedLoo && ( - - - {config.messages[message] && ( - - - {config.messages[message]} - - - )} - - - )} ); }; -HomePage.propTypes = { - initialPosition: PropTypes.shape({ - lat: PropTypes.number, - lng: PropTypes.number, - }), -}; - export default withApollo(HomePage as NextPage); diff --git a/src/pages/loos/[id].tsx b/src/pages/loos/[id].tsx deleted file mode 100644 index ff8bacd89..000000000 --- a/src/pages/loos/[id].tsx +++ /dev/null @@ -1,4 +0,0 @@ -import { NextPage } from 'next'; -import { withApollo } from '../../components/withApollo'; -import HomePage from '../index'; -export default HomePage as NextPage; diff --git a/src/pages/loos/[id]/index.tsx b/src/pages/loos/[id]/index.tsx new file mode 100644 index 000000000..1556eae83 --- /dev/null +++ b/src/pages/loos/[id]/index.tsx @@ -0,0 +1,152 @@ +import React from 'react'; +import Head from 'next/head'; +import dynamic from 'next/dynamic'; +import PageLayout from '../../../components/PageLayout'; +import Box from '../../../components/Box'; +import Sidebar from '../../../components/Sidebar'; +import Notification from '../../../components/Notification'; +import VisuallyHidden from '../../../components/VisuallyHidden'; +import ToiletDetailsPanel from '../../../components/ToiletDetailsPanel'; +import { useMapState } from '../../../components/MapState'; +import config from '../../../config'; +import { useRouter } from 'next/router'; +import { withApollo } from '../../../components/withApollo'; +import { NextPage } from 'next'; +import useFilters from '../../../components/useFilters'; +import { + useFindLoosNearbyQuery, + useFindLooByIdQuery, +} from '../../../api-client/graphql'; + +const SIDEBAR_BOTTOM_MARGIN = 32; +const MapLoader = () =>

Loading map...

; +const LooMap = dynamic(() => import('../../../components/LooMap'), { + loading: MapLoader, + ssr: false, +}); + +const LooPage = () => { + const [mapState, setMapState] = useMapState(); + const router = useRouter(); + const { id, message } = router.query; + + const { data: looData, loading } = useFindLooByIdQuery({ + variables: { + id: id as string, + }, + }); + + const { data: nearbyLoos } = useFindLoosNearbyQuery({ + variables: { + lat: looData?.loo.location.lat, + lng: looData?.loo.location.lng, + radius: Math.ceil(mapState.radius), + }, + skip: !looData?.loo, + }); + + const { filters, filtered, setFilters } = useFilters( + nearbyLoos?.loosByProximity + ); + + // set initial map center to toilet if on /loos/:id + // React.useEffect(() => { + // if (shouldCenter && selectedLoo) { + // setMapState({ + // center: selectedLoo.location, + // }); + + // // don't recenter the map each time the id changes + // setShouldCenter(false); + // } + // }, [selectedLoo, shouldCenter, setMapState]); + + const [toiletPanelDimensions, setToiletPanelDimensions] = React.useState({}); + + const pageTitle = config.getTitle(looData?.loo.name || 'Unnamed Toilet'); + + return ( + + + {pageTitle} + + + +

{pageTitle}

+
+ + + + setMapState({ center })} + onUpdateMapPosition={setMapState} + mapCenter={mapState.center} + /> + + + { + // if (toilet.id === selectedLooId) { + // return { + // ...toilet, + // isHighlighted: true, + // }; + // } + // return toilet; + // })} + loos={filtered} + center={mapState.center} + zoom={mapState.zoom} + onViewportChanged={setMapState} + controlsOffset={toiletPanelDimensions.height} + /> + + + + {config.messages[message] && ( + + + {config.messages[message]} + + + )} + + + +
+ ); +}; + +export default withApollo(LooPage as NextPage); diff --git a/src/pages/map/[lng]/[lat].tsx b/src/pages/map/[lng]/[lat].tsx new file mode 100644 index 000000000..905c98cfa --- /dev/null +++ b/src/pages/map/[lng]/[lat].tsx @@ -0,0 +1,114 @@ +import React, { useState } from 'react'; +import Head from 'next/head'; +import dynamic from 'next/dynamic'; +import PageLayout from '../../../components/PageLayout'; +import Box from '../../../components/Box'; +import Sidebar from '../../../components/Sidebar'; +import VisuallyHidden from '../../../components/VisuallyHidden'; +import { useMapState } from '../../../components/MapState'; +import config, { FILTERS_KEY } from '../../../config'; +import { useRouter } from 'next/router'; +import { withApollo } from '../../../components/withApollo'; +import { NextPage } from 'next'; +import { useFindLoosNearbyQuery } from '../../../api-client/graphql'; + +const SIDEBAR_BOTTOM_MARGIN = 32; +const MapLoader = () =>

Loading map...

; +const LooMap = dynamic(() => import('../../../components/LooMap'), { + loading: MapLoader, + ssr: false, +}); + +const MapPage = () => { + const router = useRouter(); + const [mapState, setMapState] = useMapState(); + let initialState = config.getSettings(FILTERS_KEY); + + // default any unsaved filters as 'false' + config.filters.forEach((filter) => { + initialState[filter.id] = initialState[filter.id] || false; + }); + + const [filters, setFilters] = useState(initialState); + + // keep local storage and state in sync + React.useEffect(() => { + window.localStorage.setItem(FILTERS_KEY, JSON.stringify(filters)); + }, [filters]); + + const { data } = useFindLoosNearbyQuery({ + variables: { + lat: mapState.center.lat, + lng: mapState.center.lng, + radius: Math.ceil(mapState.radius), + }, + }); + + // get the filter objects from config for the filters applied by the user + const applied = config.filters.filter((filter) => filters[filter.id]); + + const toilets = React.useMemo(() => { + if (data?.loosByProximity) { + return data.loosByProximity.filter((toilet: { [x: string]: any }) => + applied.every((filter) => { + const value = toilet[filter.id]; + + if (value === null) { + return false; + } + + return !!value; + }) + ); + } + return []; + }, [data?.loosByProximity, applied]); + + const pageTitle = config.getTitle('Area Map'); + + return ( + + + {pageTitle} + + + +

{pageTitle}

+
+ + + + setMapState({ center })} + onUpdateMapPosition={setMapState} + mapCenter={mapState.center} + /> + + + + +
+ ); +}; + +export default withApollo(HomePage as NextPage);