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
2 changes: 1 addition & 1 deletion src/app/components/form-select/form-select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export default function FormSelect({
}: {
label: string;
name: string;
selectAttributes: object;
selectAttributes?: object;
options: SelectItem[];
onValueUpdate?: (v: string) => void;
}) {
Expand Down
2 changes: 2 additions & 0 deletions src/app/helpers/data.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ export function useRefreshable<T>(getter: () => T) {
return React.useReducer(getter, getter());
}

export type SetHandle = ReturnType<typeof useSet>

// Each time the Set is updated, the handle is refreshed
// That way, the Set doesn't have to be rebuilt
export function useSet<T>(initialValue:T[]=[]) {
Expand Down
1 change: 1 addition & 0 deletions src/app/helpers/window-settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export type WindowWithSettings = typeof window & {
accountHref: string;
gatedContentEndpoint: string;
renewalEndpoint: string;
mapboxPK: string;
};
};

Expand Down
7 changes: 4 additions & 3 deletions src/app/models/query-schools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export type SchoolInfo = {
testimonial?: string;
testimonial_name?: string;
testimonial_position?: string;
current_year_savings: number;
all_time_savings: number;
location: string;
total_school_enrollment: string | null;
Expand All @@ -29,13 +30,13 @@ type Item = {
fields: SchoolInfo;
};

type AugmentedInfo = {
export type AugmentedInfo = {
pk: string;
cityState: string;
institutionalPartner: string;
institutionType: string;
fields: SchoolInfo;
lngLat?: number[];
lngLat?: [number, number];
testimonial?: {
text: string;
name?: string;
Expand All @@ -51,7 +52,7 @@ function augmentInfo(item: Item) {
institutionType: item.fields.type,
fields: item.fields
};
const lngLat = [Number(item.fields.long), Number(item.fields.lat)];
const lngLat: [number, number] = [Number(item.fields.long), Number(item.fields.lat)];

if (!(lngLat[0] === 0 && lngLat[1] === 0)) {
result.lngLat = lngLat;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,31 +1,34 @@
import mapboxgl from 'mapbox-gl';
import mapboxgl, {
FilterSpecification,
LngLatBoundsLike,
LngLatLike,
MapOptions
} from 'mapbox-gl';
import mapboxPromise from '~/models/mapbox';

const settings = window.SETTINGS;
import settings from '~/helpers/window-settings';
import {AugmentedInfo} from '~/models/query-schools';

// Set up CSS once, when needed
(() => {
const cssEl = Object.assign(
document.createElement('link'),
{
rel: 'stylesheet',
href: 'https://api.tiles.mapbox.com/mapbox-gl-js/v3.9.4/mapbox-gl.css',
type: 'text/css'
}
);
const firstLink = document.querySelector('head link[rel="stylesheet"]') ||
const cssEl = Object.assign(document.createElement('link'), {
rel: 'stylesheet',
href: 'https://api.tiles.mapbox.com/mapbox-gl-js/v3.9.4/mapbox-gl.css',
type: 'text/css'
});
const firstLink =
document.querySelector('head link[rel="stylesheet"]') ||
document.querySelector('head title');

firstLink?.parentNode.insertBefore(cssEl, firstLink.nextSibling);
firstLink?.parentNode?.insertBefore(cssEl, firstLink.nextSibling);
})();

mapboxgl.accessToken = settings.mapboxPK;
mapboxgl.accessToken = settings().mapboxPK;

function hasLngLat({lngLat}) {
function hasLngLat({lngLat}: {lngLat?: unknown}) {
return Boolean(lngLat);
}

async function createMapboxMap(mapOptions) {
async function createMapboxMap(mapOptions: MapOptions) {
const mapbox = await mapboxPromise;

return new mapboxgl.Map({
Expand All @@ -34,15 +37,15 @@ async function createMapboxMap(mapOptions) {
});
}

export default function createMap(options) {
export default function createMap(options: MapOptions) {
// This is a promise that yields the MapboxGL map object
const loaded = createMapboxMap(options);
const initialState = {
center: options.center,
zoom: options.zoom
};

function setBounds(bounds) {
function setBounds(bounds?: LngLatBoundsLike) {
loaded.then((map) => {
if (bounds) {
map.fitBounds(bounds, {
Expand All @@ -55,7 +58,7 @@ export default function createMap(options) {
});
}

function setFilters(filterSpec) {
function setFilters(filterSpec?: FilterSpecification) {
loaded.then((map) => {
map.setFilter('os-schools', filterSpec);
map.setFilter('os-schools-shadow-at-8', filterSpec);
Expand All @@ -74,9 +77,14 @@ export default function createMap(options) {
tooltip.remove();
}

function showPoints(pointList) {
const mappable = ({lngLat: [lng, lat]}) => !(lng === 0 && lat === 0);
const mappableData = pointList.filter(hasLngLat).filter(mappable);
type HasLngLat = AugmentedInfo & Required<Pick<AugmentedInfo, 'lngLat'>>;

function showPoints(pointList: AugmentedInfo[]) {
const mappable = ({lngLat: [lng, lat]}: HasLngLat) =>
!(lng === 0 && lat === 0);
const mappableData = (
pointList.filter(hasLngLat) as HasLngLat[]
).filter(mappable);

tooltip.remove();
if (mappableData.length === 0) {
Expand All @@ -86,20 +94,24 @@ export default function createMap(options) {
(bound, obj) => bound.extend(obj.lngLat),
new mapboxgl.LngLatBounds()
);
const filterSpec = ['in', 'id', ...(mappableData.map((obj) => obj.pk))];
const filterSpec = [
'in',
'id',
...mappableData.map((obj) => obj.pk)
];

setFilters(filterSpec);
setBounds(bounds);
}
}

function showTooltip(schoolInfo, flyThere) {
function showTooltip(schoolInfo: AugmentedInfo) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothing was using flyThere

if (!hasLngLat(schoolInfo)) {
return;
}
let html = `
<div class="put-away">
<button type="button">
<button type="button" aria-label="close tooltip">
&times;
</button>
</div>
Expand All @@ -109,32 +121,27 @@ export default function createMap(options) {
if (schoolInfo.cityState) {
html += `<br>${schoolInfo.cityState}`;
}
tooltip.setLngLat(schoolInfo.lngLat);
tooltip.setLngLat(schoolInfo.lngLat as LngLatLike);
tooltip.setHTML(html);
loaded.then((map) => {
tooltip.addTo(map);
});
if (flyThere) {
setBounds((new mapboxgl.LngLatBounds()).extend(schoolInfo.lngLat));
}
}

loaded.then(
(map) => {
map.on('load', () => map.loaded() && map.resize());
map.on('mouseenter', 'os-schools', () => {
map.getCanvas().style.cursor = 'pointer';
});
map.on('mouseleave', 'os-schools', () => {
map.getCanvas().style.cursor = '';
});
map.on('click', (el) => {
if (!el.features && tooltip) {
tooltip.remove();
}
});
}
);
loaded.then((map) => {
map.on('load', () => map.loaded() && map.resize());
map.on('mouseenter', 'os-schools', () => {
map.getCanvas().style.cursor = 'pointer';
});
map.on('mouseleave', 'os-schools', () => {
map.getCanvas().style.cursor = '';
});
map.on('click', (el) => {
if (!el.features && tooltip) {
tooltip.remove();
}
});
});

return {
loaded,
Expand Down
76 changes: 0 additions & 76 deletions src/app/pages/separatemap/map-context.js

This file was deleted.

76 changes: 76 additions & 0 deletions src/app/pages/separatemap/map-context.tsx
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

File diffs are in the first commit

Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import React from 'react';
import buildContext from '~/components/jsx-helpers/build-context';
import createMap from './map-api';
import {isMobileDisplay} from '~/helpers/device';
import {AugmentedInfo, queryById} from '~/models/query-schools';
import {assertNotNull} from '~/helpers/data';

function useMap(id: string) {
const mapZoom = isMobileDisplay() ? 2 : 3;
const map = React.useMemo(
() =>
createMap({
container: id,
center: [-95.712891, 37.09024],
zoom: mapZoom,
pitchWithRotate: false,
dragRotate: false,
touchZoomRotate: false
}),
[mapZoom, id]
);

React.useEffect(() => {
const container = assertNotNull(document.getElementById('mapd'));
const clickHandler = (event: MouseEvent) => {
const delegateTarget = (event.target as Element).closest(
'.mapboxgl-popup-content .put-away'
);

if (delegateTarget) {
map.tooltip.remove();
}
};

container.addEventListener('click', clickHandler);
return () => container.removeEventListener('click', clickHandler);
}, [map]);

return map;
}

type Map = ReturnType<typeof createMap>;

function useSelectedSchool(map: Map) {
const [selectedSchool, setSelectedSchool] =
React.useState<AugmentedInfo | null>(null);
const selectSchool = React.useCallback(
(id: string) =>
queryById(id).then((schoolInfo) => setSelectedSchool(schoolInfo)),
[]
);

React.useEffect(() => {
map.loaded.then((glMap) => {
glMap.on('click', 'os-schools', (el) =>
selectSchool(el.features?.[0].properties?.id)
);
});
}, [map.loaded, selectSchool]);

return selectedSchool;
}

function useContextValue({id}: {id: string}) {
const map = useMap(id);
const selectedSchool = useSelectedSchool(map);

return {
map,
selectedSchool
};
}

const {useContext, ContextProvider} = buildContext({useContextValue});

export {useContext as default, ContextProvider as MapContextProvider};
Loading