Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 21 additions & 122 deletions src/components/location-detail/street-view.tsx
Original file line number Diff line number Diff line change
@@ -1,138 +1,37 @@
"use client";

import {
Position,
SimplifiedLocationData,
YourPeerLegacyLocationData,
} from "@/components/common";
import LocationStubMarker from "@/components/location-stub-marker";
import {
activeMarkerIcon,
defaultZoom,
mapStyles,
} from "@/components/map-common";
import { YourPeerLegacyLocationData } from "@/components/common";
import { buildStreetViewUrls } from "@/lib/streetView";
import {
APIProvider,
Map,
MapCameraChangedEvent,
Marker,
} from "@vis.gl/react-google-maps";
import { useCallback, useEffect, useState } from "react";

export default function StreetView({
location,
}: {
location: YourPeerLegacyLocationData;
}) {
const [zoom, setZoom] = useState<number>(defaultZoom);
const [mapCenter, setMapCenter] = useState<Position>(location);
const [locationStubs, setLocationStubs] = useState<SimplifiedLocationData[]>(
[],
);

const { imageUrl, mapsUrl } = buildStreetViewUrls(location, {
size: "600x500",
});

// TODO: eliminate duplicate code
const handleCameraChange = useCallback(
(ev: MapCameraChangedEvent) => {
const googleMapDiv = ev.map.getDiv();

// if google map is already hidden, then ignore the event, because we get a weird zoom
if (
!googleMapDiv ||
(googleMapDiv.clientHeight === 0 && googleMapDiv.clientWidth === 0)
)
return;

const newCenter = ev.detail.center;
if (
newCenter.lat !== 0 &&
newCenter.lng !== 0 &&
(mapCenter.lat !== newCenter.lat || mapCenter.lng !== newCenter.lng)
) {
setMapCenter(newCenter);
}

const newZoom = ev.detail.zoom;
if (newZoom && newZoom !== zoom) {
setZoom(newZoom);
}
},
[mapCenter, setMapCenter, zoom, setZoom],
);

// TODO: call the locationStubs API
useEffect(() => {}, []);

return location.closed ? undefined : (
<div>
<div>
<a
href={mapsUrl}
target="_blank"
rel="noopener noreferrer"
className="w-full max-h-72 h-72 bg-neutral-100 overflow-hidden relative hidden md:block"
>
<img
src={imageUrl}
alt=""
className="w-full h-full object-cover object-center cursor-pointer"
loading="lazy"
/>
<span className="inline-block absolute bottom-4 right-4 z-0 bg-white shadow-sm rounded-full px-5 py-2 text-dark font-medium text-sm">
Open Street View
</span>
</a>
</div>
<div className="w-full max-h-52 h-52 overflow-hidden relative md:hidden">
<div id="miniMap" className="w-full h-full bg-neutral-100">
<APIProvider
apiKey={process.env.NEXT_PUBLIC_GOOGLE_MAPS_API_KEY as string}
libraries={["marker"]}
>
<Map
defaultZoom={zoom}
gestureHandling={"greedy"}
zoomControl={false}
streetViewControl={false}
mapTypeControl={false}
fullscreenControl={false}
center={mapCenter}
styles={mapStyles}
onCameraChanged={handleCameraChange}
>
<Marker
position={location}
title={location.name}
icon={activeMarkerIcon}
/>
<span>
{locationStubs
? locationStubs
.filter((locationStub) => locationStub.id !== location.id)
.map((locationStub) => (
<LocationStubMarker
locationStub={locationStub}
key={locationStub.id}
/>
))
: undefined}
</span>
</Map>
</APIProvider>
</div>
<a
href={mapsUrl}
target="_blank"
rel="noopener noreferrer"
className="inline-block absolute bottom-4 right-4 z-0 bg-white shadow rounded-full px-5 py-2 text-dark font-medium text-sm"
>
Open Street View
</a>
</div>
</div>
<a
href={mapsUrl}
target="_blank"
rel="noopener noreferrer"
className="w-full h-52 md:h-72 bg-neutral-100 overflow-hidden relative block"
>
<img
src={imageUrl}
alt=""
className="w-full h-full object-cover object-center cursor-pointer"
width="600"
height="500"
loading="eager"
fetchPriority="high"
decoding="async"
/>
<span className="inline-block absolute bottom-4 right-4 z-0 bg-white shadow-sm rounded-full px-5 py-2 text-dark font-medium text-sm">
Open Street View
</span>
</a>
);
}
27 changes: 17 additions & 10 deletions src/components/main-component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { useViewStore } from "@/lib/store";
import classNames from "classnames";
import { usePathname } from "next/navigation";
import { Suspense } from "react";
import { Suspense, useEffect } from "react";
import { LOCATION_ROUTE } from "./common";
import FiltersPopup from "./filters-popup";
import { MapLoadingAnimation } from "./map-loading-animation";
Expand All @@ -15,26 +15,33 @@ export function MainComponent({
mapContainer: React.ReactNode;
sidePanel: React.ReactNode;
}) {
const currentPath = usePathname() as string;
const [ignore, firstPathComponent, secondPathComponent] =
currentPath.split("/");
const isLocationDetailPage =
firstPathComponent === LOCATION_ROUTE &&
typeof secondPathComponent === "string";

const showMapViewOnMobile = useViewStore(
(state) => state.showMapViewOnMobile,
);
const setShowMapViewOnMobile = useViewStore(
(state) => state.setShowMapViewOnMobile,
);
const currentPath = usePathname();
const [, firstPathComponent, secondPathComponent] = currentPath.split("/");
const isLocationDetailPage =
firstPathComponent === LOCATION_ROUTE &&
typeof secondPathComponent === "string";

const showMapView = showMapViewOnMobile && !isLocationDetailPage;
useEffect(() => {
if (isLocationDetailPage && showMapViewOnMobile) {
setShowMapViewOnMobile(false);
}
}, [isLocationDetailPage, setShowMapViewOnMobile, showMapViewOnMobile]);

const classnames = classNames([
"flex-1",
"overflow-hidden",
"flex",
"flex-col",
"md:flex-row",
showMapView ? "showMapOnMobile" : "hideMapOnMobile",
showMapViewOnMobile && !isLocationDetailPage
? "showMapOnMobile"
: "hideMapOnMobile",
]);

return (
Expand Down
11 changes: 11 additions & 0 deletions src/components/map-loading.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export function shouldLoadGoogleMap({
viewportWidth,
showMapViewOnMobile,
isLocationDetail,
}: {
viewportWidth: number;
showMapViewOnMobile: boolean;
isLocationDetail: boolean;
}): boolean {
return viewportWidth >= 768 || (showMapViewOnMobile && !isLocationDetail);
}
44 changes: 35 additions & 9 deletions src/components/map.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { defaultZoom, mapStyles, myLocationIcon } from "./map-common";
import { MobileTray } from "./mobile-tray";
import { getUrlWithNewFilterParameter } from "./navigation";
import { shouldAutoRedirectToNearby } from "./nearby-redirect";
import { shouldLoadGoogleMap } from "./map-loading";

function isMobile(): boolean {
return window.innerWidth < 768;
Expand Down Expand Up @@ -382,6 +383,29 @@ export default function LocationsMap({
useState<string | undefined>(cookieLocationSlugClickedOnMobile);
const [locationStubClickedOnMobile, setLocationStubClickedOnMobile] =
useState<SimplifiedLocationData>();
const [shouldLoadMap, setShouldLoadMap] = useState(false);
const showMapViewOnMobile = useViewStore(
(state) => state.showMapViewOnMobile,
);

useEffect(() => {
const updateMapLoading = () => {
const canLoadMap = shouldLoadGoogleMap({
viewportWidth: window.innerWidth,
showMapViewOnMobile,
isLocationDetail: !!locationDetailStub,
});

// Deferring the initial load protects mobile LCP. Once the user has
// opened the map, keep it mounted so a list/map toggle preserves its
// pan and zoom state instead of constructing a new map each time.
setShouldLoadMap((hasLoadedMap) => hasLoadedMap || canLoadMap);
};

updateMapLoading();
window.addEventListener("resize", updateMapLoading);
return () => window.removeEventListener("resize", updateMapLoading);
}, [locationDetailStub, showMapViewOnMobile]);

useEffect(() => {
if (locationSlugClickedOnMobile) {
Expand Down Expand Up @@ -432,15 +456,17 @@ export default function LocationsMap({
return (
<>
<div id="map" className="w-full h-full">
<APIProvider apiKey={GOOGLE_MAPS_API_KEY} libraries={["marker"]}>
<MapWrapper
locationStubs={locationStubs}
locationDetailStub={locationDetailStub}
locationStubClickedOnMobile={locationStubClickedOnMobile}
setLocationSlugClickedOnMobile={setLocationSlugClickedOnMobile}
locationSlugClickedOnMobile={locationSlugClickedOnMobile}
/>
</APIProvider>
{shouldLoadMap && (
<APIProvider apiKey={GOOGLE_MAPS_API_KEY} libraries={["marker"]}>
<MapWrapper
locationStubs={locationStubs}
locationDetailStub={locationDetailStub}
locationStubClickedOnMobile={locationStubClickedOnMobile}
setLocationSlugClickedOnMobile={setLocationSlugClickedOnMobile}
locationSlugClickedOnMobile={locationSlugClickedOnMobile}
/>
</APIProvider>
)}
</div>
{locationStubClickedOnMobile ? (
<MobileTray
Expand Down
Loading
Loading