Parameterize zoom experiments (#339)

* Adding ability to set flags in url
* parameterizing tile layers
This commit is contained in:
Nat Hillard 2021-07-14 11:26:12 -04:00 committed by GitHub
parent 6c8d71c5b9
commit 3cd6e06115
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 188 additions and 163 deletions

View file

@ -25,7 +25,7 @@ const J40Header = () => {
const toggleMobileNav = (): void => const toggleMobileNav = (): void =>
setMobileNavOpen((prevOpen) => !prevOpen); setMobileNavOpen((prevOpen) => !prevOpen);
const headerLinks = (flags: string[] | undefined) => { const headerLinks = (flags: {[key: string] : any} | undefined) => {
// static map of all possible menu items. Originally, it was all strings, // static map of all possible menu items. Originally, it was all strings,
// but we need to handle both onsite and offsite links. // but we need to handle both onsite and offsite links.
const menuData = new Map<string, JSX.Element>([ const menuData = new Map<string, JSX.Element>([
@ -64,7 +64,7 @@ const J40Header = () => {
// select which items from the above map to show, right now it's only two // select which items from the above map to show, right now it's only two
// possibilities so it's simple. Note: strings are used as react keys // possibilities so it's simple. Note: strings are used as react keys
const menu = const menu =
flags?.includes('sprint3') ? ('sprint3' in flags!) ?
['about', 'cejst', 'methodology', 'contact'] : ['about', 'cejst', 'methodology', 'contact'] :
['about', 'cejst', 'methodology', 'contact']; ['about', 'cejst', 'methodology', 'contact'];
// TODO: make feature flags flags work. // TODO: make feature flags flags work.

View file

@ -7,10 +7,12 @@ import maplibregl, {LngLatBoundsLike,
Popup, Popup,
LngLatLike, LngLatLike,
MapboxGeoJSONFeature} from 'maplibre-gl'; MapboxGeoJSONFeature} from 'maplibre-gl';
import mapStyle from '../data/mapStyle'; import {makeMapStyle} from '../data/mapStyle';
import PopupContent from './popupContent'; import PopupContent from './popupContent';
import * as constants from '../data/constants'; import * as constants from '../data/constants';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import {useFlags} from '../contexts/FlagContext';
import 'maplibre-gl/dist/maplibre-gl.css'; import 'maplibre-gl/dist/maplibre-gl.css';
import * as styles from './J40Map.module.scss'; import * as styles from './J40Map.module.scss';
@ -27,11 +29,12 @@ const J40Map = () => {
const mapRef = useRef<Map>() as React.MutableRefObject<Map>; const mapRef = useRef<Map>() as React.MutableRefObject<Map>;
const selectedFeature = useRef<MapboxGeoJSONFeature>(); const selectedFeature = useRef<MapboxGeoJSONFeature>();
const [zoom, setZoom] = useState(constants.GLOBAL_MIN_ZOOM); const [zoom, setZoom] = useState(constants.GLOBAL_MIN_ZOOM);
const flags = useFlags();
useEffect(() => { useEffect(() => {
const initialMap = new Map({ const initialMap = new Map({
container: mapContainer.current!, container: mapContainer.current!,
style: mapStyle, style: makeMapStyle(flags),
center: constants.DEFAULT_CENTER as LngLatLike, center: constants.DEFAULT_CENTER as LngLatLike,
zoom: zoom, zoom: zoom,
minZoom: constants.GLOBAL_MIN_ZOOM, minZoom: constants.GLOBAL_MIN_ZOOM,

View file

@ -6,7 +6,7 @@ describe('URL params are parsed and passed to children', () => {
describe('when the URL has a "flags" parameter set', () => { describe('when the URL has a "flags" parameter set', () => {
// We artificially set the URL to localhost?flags=1,2,3 // We artificially set the URL to localhost?flags=1,2,3
beforeEach(() => { beforeEach(() => {
window.history.pushState({}, 'Test Title', '/?flags=1,2,3'); window.history.pushState({}, 'Test Title', '/?flags=1,2,3,test=4');
}); });
describe('when using useFlags', () => { describe('when using useFlags', () => {
beforeEach(() => { beforeEach(() => {
@ -14,10 +14,11 @@ describe('URL params are parsed and passed to children', () => {
const flags = useFlags(); const flags = useFlags();
return ( return (
<> <>
<div>{flags.includes('1') ? 'yes1' : 'no1'}</div> <div>{'1' in flags ? 'yes1' : 'no1'}</div>
<div>{flags.includes('2') ? 'yes2' : 'no2'}</div> <div>{'2' in flags ? 'yes2' : 'no2'}</div>
<div>{flags.includes('3') ? 'yes3' : 'no3'}</div> <div>{'3' in flags ? 'yes3' : 'no3'}</div>
<div>{flags.includes('4') ? 'yes4' : 'no4'}</div> <div>{'4' in flags ? 'yes4' : 'no4'}</div>
<div>{flags['test'] == 4 ? 'yes5' : 'no5'}</div>
</> </>
); );
}; };
@ -33,6 +34,7 @@ describe('URL params are parsed and passed to children', () => {
expect(screen.queryByText('yes2')).toBeInTheDocument(); expect(screen.queryByText('yes2')).toBeInTheDocument();
expect(screen.queryByText('yes3')).toBeInTheDocument(); expect(screen.queryByText('yes3')).toBeInTheDocument();
expect(screen.queryByText('yes4')).not.toBeInTheDocument(); expect(screen.queryByText('yes4')).not.toBeInTheDocument();
expect(screen.queryByText('yes5')).toBeInTheDocument();
}); });
}); });
}); });

View file

@ -1,6 +1,8 @@
import * as React from 'react'; import * as React from 'react';
import * as queryString from 'query-string'; import * as queryString from 'query-string';
export type FlagContainer = { [key: string]: any };
/** /**
* FlagContext stores feature flags and passes them to consumers * FlagContext stores feature flags and passes them to consumers
*/ */
@ -8,7 +10,7 @@ import * as queryString from 'query-string';
/** /**
* Contains a list of all currently-active flags * Contains a list of all currently-active flags
*/ */
flags: string[]; flags: FlagContainer;
} }
const FlagContext = React.createContext<IFlagContext>({flags: []}); const FlagContext = React.createContext<IFlagContext>({flags: []});
@ -16,9 +18,9 @@ const FlagContext = React.createContext<IFlagContext>({flags: []});
/** /**
* `useFlags` returns all feature flags. * `useFlags` returns all feature flags.
* *
* @return {Flags[]} flags All project feature flags * @return {FlagContainer} flags All project feature flags
*/ */
const useFlags = () : string[] => { const useFlags = () : FlagContainer => {
const {flags} = React.useContext(FlagContext); const {flags} = React.useContext(FlagContext);
return flags; return flags;
}; };
@ -39,9 +41,18 @@ interface IURLFlagProviderProps {
**/ **/
const URLFlagProvider = ({children, location}: IURLFlagProviderProps) => { const URLFlagProvider = ({children, location}: IURLFlagProviderProps) => {
const flagString = queryString.parse(location.search).flags; const flagString = queryString.parse(location.search).flags;
let flags: string[] = []; const flags : FlagContainer = {};
let flagList: string[] = [];
if (flagString && typeof flagString === 'string') { if (flagString && typeof flagString === 'string') {
flags = (flagString as string).split(','); flagList = (flagString as string).split(',');
}
for (const flag of flagList) {
if (flag.includes('=')) {
const [key, value] = flag.split('=');
flags[key] = value;
} else {
flags[flag] = true;
}
} }
console.log(JSON.stringify(location), JSON.stringify(flags)); console.log(JSON.stringify(location), JSON.stringify(flags));

View file

@ -1,8 +1,11 @@
// URLS // URLS
export const FEATURE_TILE_BASE_URL = 'https://d2zjid6n5ja2pt.cloudfront.net'; export const FEATURE_TILE_BASE_URL = 'https://d2zjid6n5ja2pt.cloudfront.net';
const XYZ_SUFFIX = '{z}/{x}/{y}.pbf'; const XYZ_SUFFIX = '{z}/{x}/{y}.pbf';
export const FEATURE_TILE_HIGH_ZOOM_URL = `${FEATURE_TILE_BASE_URL}/0629_demo/${XYZ_SUFFIX}`; export const featureURLForTilesetName = (tilesetName :string ) : string => {
export const FEATURE_TILE_LOW_ZOOM_URL = `${FEATURE_TILE_BASE_URL}/tiles_low/${XYZ_SUFFIX}`; return `${FEATURE_TILE_BASE_URL}/${tilesetName}/${XYZ_SUFFIX}`;
};
export const FEATURE_TILE_HIGH_ZOOM_URL = featureURLForTilesetName('0629_demo');
export const FEATURE_TILE_LOW_ZOOM_URL = featureURLForTilesetName('tiles_low');
// Performance markers // Performance markers

View file

@ -1,6 +1,7 @@
import {Style, FillPaint} from 'maplibre-gl'; import {Style, FillPaint} from 'maplibre-gl';
import chroma from 'chroma-js'; import chroma from 'chroma-js';
import * as constants from '../data/constants'; import * as constants from '../data/constants';
import {FlagContainer} from '../contexts/FlagContext';
// eslint-disable-next-line require-jsdoc // eslint-disable-next-line require-jsdoc
function hexToHSLA(hex:string, alpha:number) { function hexToHSLA(hex:string, alpha:number) {
@ -45,7 +46,8 @@ function makePaint({
const imageSuffix = constants.isMobile ? '' : '@2x'; const imageSuffix = constants.isMobile ? '' : '@2x';
const mapStyle : Style = { export const makeMapStyle = (flagContainer: FlagContainer) : Style => {
return {
'version': 8, 'version': 8,
'sources': { 'sources': {
'carto': { 'carto': {
@ -77,6 +79,8 @@ const mapStyle : Style = {
// The below line promotes the GEOID10 property to the ID // The below line promotes the GEOID10 property to the ID
'promoteId': constants.GEOID_PROPERTY, 'promoteId': constants.GEOID_PROPERTY,
'tiles': [ 'tiles': [
'high_tiles' in flagContainer ?
constants.featureURLForTilesetName(flagContainer['high_tiles']) :
constants.FEATURE_TILE_HIGH_ZOOM_URL, constants.FEATURE_TILE_HIGH_ZOOM_URL,
], ],
// Seeting maxzoom here enables 'overzooming' // Seeting maxzoom here enables 'overzooming'
@ -93,6 +97,8 @@ const mapStyle : Style = {
'type': 'vector', 'type': 'vector',
'promoteId': constants.GEOID_PROPERTY, 'promoteId': constants.GEOID_PROPERTY,
'tiles': [ 'tiles': [
'low_tiles' in flagContainer ?
constants.featureURLForTilesetName(flagContainer['low_tiles']) :
constants.FEATURE_TILE_LOW_ZOOM_URL, constants.FEATURE_TILE_LOW_ZOOM_URL,
// For local development, use: // For local development, use:
// 'http://localhost:8080/data/tl_2010_bg_with_data/{z}/{x}/{y}.pbf', // 'http://localhost:8080/data/tl_2010_bg_with_data/{z}/{x}/{y}.pbf',
@ -212,5 +218,5 @@ const mapStyle : Style = {
}, },
], ],
}; };
};
export default mapStyle;