Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,20 @@ interface PooledMap {
pins: google.maps.Marker[];
listeners: google.maps.MapsEventListener[];
markersKey: string | null;
markerIds: Set<string>;
frame: MapFrame | null;
onSelectHref: ((href: string) => void) | null;
borrowed: boolean;
returnedAt: number;
}

// Where the camera sits when a set of markers is first shown - all of them in
// view - which is also where the "zoom to fit" control puts it back.
interface MapFrame {
bounds: google.maps.LatLngBounds;
onlyMarker: google.maps.LatLngLiteral | null;
}

interface MapsDrawing {
Marker: typeof google.maps.Marker;
LatLngBounds: typeof google.maps.LatLngBounds;
Expand All @@ -80,6 +89,8 @@ export interface BorrowedMap {
attachTo(container: HTMLElement): void;
/** Draws ``markers``, doing nothing at all if they are already drawn. */
showMarkers(markers: LocationMapMarker[]): void;
/** Puts the camera back where it sat when these markers first appeared. */
resetView(): void;
/** Gives the map back to the pool. The map itself stays alive. */
giveBack(): void;
}
Expand Down Expand Up @@ -114,6 +125,9 @@ export function borrowLocationsMap(
showMarkers(markers: LocationMapMarker[]): void {
showMarkers(borrowed, markers);
},
resetView(): void {
resetView(borrowed);
},
giveBack(): void {
if (givenBack) {
return;
Expand Down Expand Up @@ -144,6 +158,8 @@ function newPooledMap(poolKey: string, apiKey: string): PooledMap {
pins: [],
listeners: [],
markersKey: null,
markerIds: new Set(),
frame: null,
onSelectHref: null,
borrowed: false,
returnedAt: 0,
Expand Down Expand Up @@ -181,6 +197,24 @@ function showMarkers(pooled: PooledMap, markers: LocationMapMarker[]): void {
});
}

function resetView(pooled: PooledMap): void {
void ensureMap(pooled).then((map) => {
if (map === null || pooled.frame === null) {
return;
}
applyFrame(map, pooled.frame);
});
}

function applyFrame(map: google.maps.Map, frame: MapFrame): void {
if (frame.onlyMarker !== null) {
map.setCenter(frame.onlyMarker);
map.setZoom(10);
return;
}
map.fitBounds(frame.bounds);
}

function ensureMap(pooled: PooledMap): Promise<google.maps.Map | null> {
if (pooled.loading === null) {
pooled.loading = buildMap(pooled);
Expand Down Expand Up @@ -218,6 +252,11 @@ function drawMarkers(
map: google.maps.Map,
markers: LocationMapMarker[],
): void {
const markerIds = new Set(markers.map((marker) => marker.id));
const showsSomewhereElse =
pooled.markerIds.size === 0 ||
!markers.some((marker) => pooled.markerIds.has(marker.id));

for (const listener of pooled.listeners) {
listener.remove?.();
}
Expand All @@ -228,6 +267,8 @@ function drawMarkers(
pooled.pins = [];

if (drawing === null || markers.length === 0) {
pooled.markerIds = new Set();
pooled.frame = null;
return;
}
const { Marker, LatLngBounds, Size, Point } = drawing;
Expand Down Expand Up @@ -262,11 +303,21 @@ function drawMarkers(
}
}

if (markers.length > 1) {
map.fitBounds(bounds);
} else {
map.setCenter({ lat: markers[0].latitude, lng: markers[0].longitude });
map.setZoom(10);
pooled.frame = {
bounds,
onlyMarker:
markers.length === 1
? { lat: markers[0].latitude, lng: markers[0].longitude }
: null,
};
pooled.markerIds = markerIds;

// Adding or removing a location leaves the reader looking at whatever they
// had zoomed in on - it is their camera, not ours, once they have moved it.
// A wholly different set of places, though, means a different subject
// altogether - another entity's map, say - so that one gets framed afresh.
if (showsSomewhereElse) {
applyFrame(map, pooled.frame);
}
}

Expand Down Expand Up @@ -315,6 +366,8 @@ function dropMap(pooled: PooledMap): void {
pooled.map = null;
pooled.loading = null;
pooled.markersKey = null;
pooled.markerIds = new Set();
pooled.frame = null;
pooled.host.remove();

const pool = POOLS.get(pooled.poolKey);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { JupiterLocationResolver, type Location } from "@jupiter/webapi-client";
import { Box, Typography } from "@mui/material";
import { useContext, useEffect, useRef } from "react";
import { ZoomOutMap as ZoomOutMapIcon } from "@mui/icons-material";
import { Box, IconButton, Tooltip, Typography } from "@mui/material";
import { useCallback, useContext, useEffect, useRef } from "react";

import type {
BorrowedMap,
Expand Down Expand Up @@ -92,6 +93,10 @@ export function LocationsMap({
};
}, [canShowMap, cacheKey, apiKey]);

const handleResetView = useCallback(() => {
borrowedRef.current?.resetView();
}, []);

// A revalidation hands out a fresh array holding the very same locations, so
// let the map decide whether anything actually changed - it redraws only
// when the pins themselves differ, and never touches the camera otherwise.
Expand All @@ -112,7 +117,6 @@ export function LocationsMap({
</Typography>
) : null}
<Box
ref={containerRef}
sx={{
position: "relative",
width: "100%",
Expand All @@ -132,7 +136,35 @@ export function LocationsMap({
}
: undefined),
}}
/>
>
{/* The map itself goes in here, and only in here - the pool swaps out
everything below this node, so the control has to sit beside it. */}
<Box ref={containerRef} sx={{ position: "absolute", inset: 0 }} />

{canShowMap ? (
<Tooltip title="Show all locations">
<IconButton
type="button"
size="small"
aria-label="Show all locations"
onClick={handleResetView}
sx={{
position: "absolute",
top: 8,
right: 8,
zIndex: 1,
backgroundColor: "background.paper",
boxShadow: 1,
"&:hover": {
backgroundColor: "background.paper",
},
}}
>
<ZoomOutMapIcon fontSize="small" />
</IconButton>
</Tooltip>
) : null}
</Box>
</Box>
);
}
Loading