Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Perceived performance improvement with Mapbox Static Images API #331

Open
wants to merge 28 commits into
base: release/v1.1.0
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
ab88584
Mapbox Component (#267)
alextaing Oct 5, 2022
8c677c6
mapbox component jest tests (#311)
yen-tt Oct 10, 2022
69327c1
add react-dom as peer dep (#313)
yen-tt Oct 11, 2022
731c9bd
docs(MapboxMap): add storybook stories (#312)
tatimblin Oct 18, 2022
0d78e7e
use createRoot when possible for rendering pin components (#319)
oshi97 Oct 28, 2022
f585a30
Revert "use createRoot when possible for rendering pin components (#3…
oshi97 Nov 4, 2022
2c8a500
display mapbox static image until the interactive map is loaded
Nov 9, 2022
a926516
function for url, change interface
Nov 10, 2022
7b89a4f
add story for static image before load
Nov 10, 2022
0bb3220
refactor from hook to component
Nov 10, 2022
261facb
mock response to be error instead of delay
Nov 10, 2022
8faf428
remove msw setup, story use MapboxStaticImage component
Nov 10, 2022
28727e4
wait for map to load for non static map stories
Nov 10, 2022
bb25f48
ts
Nov 10, 2022
91089ce
update percy config in story
Nov 10, 2022
3d5a491
test
Nov 10, 2022
2c70739
hide getCoordinate and onDrag control
Nov 14, 2022
8ad916a
for snapshot of dynamic map with play function
Nov 14, 2022
7ab0c0c
Merge branch 'develop' into dev/mapbox-static-image
Nov 15, 2022
44f0e4a
update package lock for test-site
Nov 15, 2022
def501e
remove play function -- doesnt deplay snapshot
Nov 15, 2022
3538bfb
test dev wcag gh workflow with mapbox api key input
Nov 16, 2022
57f505f
test
Nov 16, 2022
139ee22
remove logs
Nov 16, 2022
6b48f80
update workflow to point back to v1 branch, run WCAG in feature branc…
Nov 16, 2022
06c7725
Fix wcag github action mapbox issues (#337)
yen-tt Nov 17, 2022
1ead573
Fix visual coverage test in Github Actions (#339)
yen-tt Nov 18, 2022
ffe64e5
periods
Nov 18, 2022
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
1 change: 1 addition & 0 deletions .github/workflows/coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ jobs:
test_script: npm run test
secrets:
caller_github_token: ${{ secrets.GITHUB_TOKEN }}
MAPBOX_API_KEY: ${{ secrets.MAPBOX_API_KEY }}
individual_coverage_percentages:
if: ${{ github.event_name == 'pull_request' }}
runs-on: ubuntu-latest
Expand Down
6 changes: 6 additions & 0 deletions .github/workflows/run-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,15 @@ jobs:
npm i -D react@18 react-dom@18
npm run build
node_matrix: '["14.x", "16.x", "18.x"]'
secrets:
MAPBOX_API_KEY: ${{ secrets.MAPBOX_API_KEY }}

call_run_tests-react-17:
uses: yext/slapshot-reusable-workflows/.github/workflows/run_tests.yml@v1
with:
node_matrix: '["14.x", "16.x", "18.x"]'
secrets:
MAPBOX_API_KEY: ${{ secrets.MAPBOX_API_KEY }}

call_run_tests-react-16:
uses: yext/slapshot-reusable-workflows/.github/workflows/run_tests.yml@v1
Expand All @@ -26,3 +30,5 @@ jobs:
npm i -D react@16.14 react-dom@16.14
npm run build
node_matrix: '["14.x", "16.x", "18.x"]'
secrets:
MAPBOX_API_KEY: ${{ secrets.MAPBOX_API_KEY }}
3 changes: 2 additions & 1 deletion .github/workflows/wcag_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ name: WCAG tests

on:
pull_request:
branches: [main, develop]
branches: [main, develop, feature/*]

jobs:
yen-tt marked this conversation as resolved.
Show resolved Hide resolved
call_wcag_test:
uses: yext/slapshot-reusable-workflows/.github/workflows/wcag_test.yml@v1
secrets:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
MAPBOX_API_KEY: ${{ secrets.MAPBOX_API_KEY }}
with:
build_script: ./tests/scripts/start-storybook.sh
34 changes: 21 additions & 13 deletions .storybook/test-runner.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,36 @@
import { injectAxe, checkA11y } from 'axe-playwright';
import { Page } from 'playwright-core';
import { runOnly } from './wcagConfig';
import { TestContext, TestRunnerConfig } from '@storybook/test-runner';

/**
* See https://storybook.js.org/docs/react/writing-tests/test-runner#test-hook-api-experimental
* to learn more about the test-runner hooks API.
*/
const renderFunctions = {
const renderFunctions: TestRunnerConfig = {
async preRender(page: Page) {
await injectAxe(page);
},
async postRender(page: Page, context) {
await checkA11y(page, '#root', {
axeOptions: {
runOnly,
rules: {
'color-contrast': { enabled: context.name !== 'Loading' }
},
},
detailedReport: true,
detailedReportOptions: {
html: true,
async postRender(page: Page, context: TestContext) {
await checkA11y(
page,
{
include: ['#root'],
exclude: ['#root .mapboxgl-canvas-container'],
},
});
{
axeOptions: {
runOnly,
rules: {
'color-contrast': { enabled: context.name !== 'Loading' },
},
},
detailedReport: true,
detailedReportOptions: {
html: true,
},
}
);
},
};

Expand Down
37 changes: 26 additions & 11 deletions src/components/MapboxMap.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { useRef, useEffect } from 'react';
import { useRef, useEffect, useMemo } from 'react';
import mapboxgl, { Map, Marker, MapboxOptions, LngLatBounds, MarkerOptions, LngLat } from 'mapbox-gl';
import { Result, useSearchState } from '@yext/search-headless-react';
import { useDebouncedFunction } from '../hooks/useDebouncedFunction';
import ReactDOM from 'react-dom';
import { MapboxStaticImage } from './MapboxStaticImage';

/**
* A functional component that can be used to render a custom marker on the map.
Expand Down Expand Up @@ -103,25 +104,33 @@ export function MapboxMap<T>({
const locationResults = useSearchState(state => state.vertical.results) as Result<T>[];
const onDragDebounced = useDebouncedFunction(onDrag, 100);

const options: Omit<MapboxOptions, 'container'> = useMemo(() => {
return {
style: 'mapbox://styles/mapbox/streets-v11?optimize=true',
center: [-74.005371, 40.741611],
zoom: 9,
...mapboxOptions
};
}, [mapboxOptions]);

useEffect(() => {
if (mapContainer.current && !map.current) {
const options: MapboxOptions = {
const mapbox = new Map({
container: mapContainer.current,
style: 'mapbox://styles/mapbox/streets-v11',
center: [-74.005371, 40.741611],
zoom: 9,
...mapboxOptions
};
map.current = new Map(options);
const mapbox = map.current;
...options
});
map.current = mapbox;
mapbox.resize();
if (onDragDebounced) {
mapbox.on('drag', () => {
onDragDebounced(mapbox.getCenter(), mapbox.getBounds());
});
}
mapbox.on('load', () => {
mapbox.getContainer().style.visibility = 'visible';
});
}
}, [mapboxOptions, onDragDebounced]);
}, [options, onDragDebounced]);

useEffect(() => {
markers.current.forEach(marker => marker.remove());
Expand Down Expand Up @@ -161,7 +170,13 @@ export function MapboxMap<T>({
}, [PinComponent, getCoordinate, locationResults]);

return (
<div ref={mapContainer} className='h-full w-full' />
<div className='grid h-full w-full'>
<div ref={mapContainer} className="col-span-full row-span-full invisible"/>
<MapboxStaticImage
mapboxAccessToken={mapboxAccessToken}
mapboxOptions={options}
/>
</div>
);
}

Expand Down
81 changes: 81 additions & 0 deletions src/components/MapboxStaticImage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { CSSProperties, useMemo, useRef, useState } from 'react';
import { MapboxOptions, LngLatLike } from 'mapbox-gl';
import useLayoutEffect from 'use-isomorphic-layout-effect';

/**
* Props for {@link MapboxStaticImage}.
*/
export interface MapboxStaticImageProps {
/** Mapbox access token. */
mapboxAccessToken: string,
/** Map customization for Mapbox static image. */
mapboxOptions: Pick<MapboxOptions, 'center' | 'zoom' | 'style'>
}

/**
* Renders a static map image using Mapbox Static Image API.
*
* @internal
*/
export function MapboxStaticImage({
mapboxAccessToken,
mapboxOptions
}: MapboxStaticImageProps): JSX.Element | null {
const mapContainer = useRef<HTMLDivElement>(null);
const [divDimension, setDivDimension] = useState<{ width: number, height: number }>();
useLayoutEffect(() => {
if (mapContainer.current) {
setDivDimension({
width: mapContainer.current.clientWidth,
height: mapContainer.current.clientHeight
});
}
}, []);

const staticMapboxStyle: CSSProperties | undefined = useMemo(() => {
const { style, center, zoom } = mapboxOptions;
if (!divDimension || !center || !zoom || typeof style !== 'string') {
return undefined;
}
const { width, height } = divDimension;
const url = getMapboxStaticImageUrl({ mapboxAccessToken, style, center, zoom, width, height });
if (!url) {
return undefined;
}
return {
backgroundImage: `url(${url})`
};
}, [divDimension, mapboxAccessToken, mapboxOptions]);

return <div ref={mapContainer} className='col-span-full row-span-full' style={staticMapboxStyle}/>;
}

/**
* The configuration for Mapbox Static Image API url.
*/
interface MapboxStaticImageUrlConfig {
/** Mapbox access token. */
mapboxAccessToken: string,
/** Mapbox stylesheet url to apply to the static map. */
style: string,
/** Center point of the static map. */
center: LngLatLike,
/** Zoom level of the static map. */
zoom: number,
/** Width (in pixels) of the image. */
width: number,
/** Height (in pixels) of the image. */
height: number
}

function getMapboxStaticImageUrl(urlConfig: MapboxStaticImageUrlConfig): string | undefined {
const { mapboxAccessToken, style, center, zoom, width, height } = urlConfig;
// Mapbox Static Image API only support width and height between 1-1280 pixels.
if (width > 1280 || height > 1280) {
return undefined;
}
const stylesheet = style.split('mapbox://styles/')[1].split('?')[0];
const centerAndZoom = `${center[0]},${center[1]},${zoom}`;
const dimension = `${width}x${height}`;
return `https://api.mapbox.com/styles/v1/${stylesheet}/static/${centerAndZoom}/${dimension}?access_token=${mapboxAccessToken}`;
}
Loading