Skip to content

Commit a723a3f

Browse files
authored
Merge pull request #1137 from jboolean/tote-bag-frontend
Tote bag frontend rendering
2 parents 547b85f + 9772130 commit a723a3f

File tree

10 files changed

+13533
-31975
lines changed

10 files changed

+13533
-31975
lines changed

backend/package-lock.json

Lines changed: 456 additions & 22933 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frontend/package-lock.json

Lines changed: 12857 additions & 9034 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frontend/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"@sentry/react": "^7.119.2",
2222
"@sentry/tracing": "^7.114.0",
2323
"@stripe/stripe-js": "^1.42.0",
24+
"@turf/turf": "^7.2.0",
2425
"@types/grecaptcha": "^3.0.4",
2526
"@types/react-router": "^5.1.20",
2627
"axios": "^1.7.9",

frontend/src/screens/App/index.tsx

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import AnnouncementBanner from './screens/AnnouncementBanner';
66
import MapPane from './screens/MapPane';
77
import Shutdown from './screens/Shutdown';
88
import ThankYou from './screens/TipJar/ThankYou';
9+
import ToteBag from './screens/ToteBag';
910
import ViewerPane from './screens/ViewerPane';
1011
import Welcome from './screens/Welcome';
1112

@@ -16,14 +17,14 @@ import Outtakes from './screens/Outtakes';
1617
import { OptimizeExperimentsProvider } from 'shared/utils/OptimizeExperiments';
1718
import 'utils/optimize';
1819
import AdminRoutes from './screens/Admin/AdminRoutes';
20+
import Corrections from './screens/Corrections';
1921
import CreditPurchaseModal, {
2022
CreditPurchaseSuccessMessage,
2123
} from './screens/CreditPurchaseModal';
2224
import EditStory from './screens/EditStory';
2325
import FeatureFlags from './screens/FeatureFlags';
2426
import SubmitStoryWizard from './screens/SubmitStoryWizard';
2527
import TipJar, { useTipJarStore } from './screens/TipJar';
26-
import Corrections from './screens/Corrections';
2728

2829
const IS_SHUTDOWN = false;
2930

@@ -33,8 +34,10 @@ const thankYouInitial = searchParams.has('tipSuccess');
3334
const creditSuccessInitial = searchParams.has('creditPurchaseSuccess');
3435
const openTipJarOnLoad = searchParams.has('openTipJar');
3536
const noWelcome = searchParams.has('noWelcome');
37+
const noTipJar = searchParams.has('noTipJar');
3638

3739
if (noWelcome) searchParams.delete('noWelcome');
40+
if (noTipJar) searchParams.delete('noTipJar');
3841
history.replace({
3942
pathname: history.location.pathname,
4043
hash: history.location.hash,
@@ -87,7 +90,7 @@ function Modals(): JSX.Element {
8790
setCreditPurchaseSuccessOpen(false);
8891
}}
8992
/>
90-
<TipJar />
93+
{noTipJar ? null : <TipJar />}
9194
</>
9295
);
9396
}
@@ -96,8 +99,13 @@ export default function App(): JSX.Element {
9699
return (
97100
<OptimizeExperimentsProvider>
98101
<div className={stylesheet.outermostContainer}>
99-
<AnnouncementBanner />
100102
<Router history={history}>
103+
<Switch>
104+
<Route path="/render-merch"></Route>
105+
<Route>
106+
<AnnouncementBanner />
107+
</Route>
108+
</Switch>
101109
<div className={stylesheet.mainContentWrapper}>
102110
<div className={stylesheet.mainContentContainer}>
103111
<Modals />
@@ -129,6 +137,9 @@ export default function App(): JSX.Element {
129137
<Route path="/admin">
130138
<AdminRoutes />
131139
</Route>
140+
<Route path="/render-merch/tote-bag">
141+
<ToteBag />
142+
</Route>
132143
{!IS_SHUTDOWN && <Redirect to="/map" />}
133144
</Switch>
134145
</div>

frontend/src/screens/App/screens/MapPane/components/MainMap/Borough Boundaries simplified.json

Lines changed: 7 additions & 0 deletions
Large diffs are not rendered by default.

frontend/src/screens/App/screens/MapPane/components/MainMap/index.tsx

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,19 @@ import React from 'react';
33
import {
44
// withRouter,
55
useHistory,
6-
useRouteMatch,
76
useLocation,
7+
useRouteMatch,
88
} from 'react-router-dom';
99

10-
import mapboxgl from 'mapbox-gl';
1110
import classnames from 'classnames';
11+
import mapboxgl from 'mapbox-gl';
1212

1313
import * as overlays from './overlays';
1414

1515
export { OverlayId } from './overlays';
1616

17-
import stylesheet from './MainMap.less';
1817
import { RouteComponentProps } from 'react-router';
18+
import stylesheet from './MainMap.less';
1919

2020
const MAPBOX_STYLE = __DEV__
2121
? 'mapbox://styles/julianboilen/ck5jrzrs11r1p1imia7qzjkm1/draft'
@@ -77,6 +77,12 @@ class MainMap extends React.PureComponent<PropsWithRouter> {
7777

7878
this.syncUI();
7979
});
80+
81+
// Added to remove layers outside the viewport, to make the attribution correct
82+
map.on('moveend', () => {
83+
if (!map.isStyleLoaded()) return;
84+
this.syncUI();
85+
});
8086
}
8187

8288
componentDidUpdate(prevProps: PropsWithRouter): void {

frontend/src/screens/App/screens/MapPane/components/MainMap/overlays.ts

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
// import canUseWebP from 'utils/canUseWebP';
22

3+
import { bboxPolygon, booleanIntersects } from '@turf/turf';
4+
import { Feature } from 'geojson';
5+
import boroughBoundaries from './Borough Boundaries simplified.json';
6+
37
const LAYER_IDS = [
48
'arial-1924',
59
'arial-1951',
@@ -21,6 +25,16 @@ export type OverlayId =
2125
| 'atlas-1930'
2226
| 'atlas-1956';
2327

28+
const MANHATTAN_BOUNDS_GEOJSON = boroughBoundaries.features.find(
29+
(feature) => feature.properties['boro_name'] === 'Manhattan'
30+
) as Feature;
31+
32+
const attributionBoundaries: Partial<Record<LayerId, Feature>> = {
33+
'atlas-1930': MANHATTAN_BOUNDS_GEOJSON,
34+
'atlas-1956': MANHATTAN_BOUNDS_GEOJSON,
35+
'atlas-1916': MANHATTAN_BOUNDS_GEOJSON,
36+
};
37+
2438
// For tiles recompressed at edge
2539
// const ext = canUseWebP() ? 'webp' : 'png';
2640

@@ -33,7 +47,7 @@ const overlaysToLayers: { [overlay in OverlayId]: LayerId[] } = {
3347
},
3448
get 'default-arial'() {
3549
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-return
36-
return [...this['default-map'], ...this['arial-1951']];
50+
return [...this['arial-1951']];
3751
},
3852
'arial-1924': ['arial-1924', 'nyc-label'],
3953
'arial-1951': ['arial-1951', 'nyc-label'],
@@ -134,11 +148,25 @@ export const setOverlay = (
134148
overlayId: OverlayId | null
135149
): void => {
136150
const visibleLayers = overlayId ? overlaysToLayers[overlayId] : [];
151+
const mapBounds = map.getBounds();
152+
153+
// Convert the map bounds into a Turf polygon
154+
const viewportPolygon = bboxPolygon([
155+
mapBounds.getWest(),
156+
mapBounds.getSouth(),
157+
mapBounds.getEast(),
158+
mapBounds.getNorth(),
159+
]);
137160
LAYER_IDS.forEach((layerId) => {
161+
const layerAttributionBounds = attributionBoundaries[layerId];
138162
map.setLayoutProperty(
139163
layerId,
140164
'visibility',
141-
visibleLayers.includes(layerId) ? 'visible' : 'none'
165+
visibleLayers.includes(layerId) &&
166+
(!layerAttributionBounds ||
167+
booleanIntersects(viewportPolygon, layerAttributionBounds))
168+
? 'visible'
169+
: 'none'
142170
);
143171
});
144172
};
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
@import '../../../../shared/styles/colors.less';
2+
@import '../../../../shared/styles/fonts.less';
3+
4+
:local {
5+
@dpi: 150px;
6+
7+
.container {
8+
display: block;
9+
width: 17 * @dpi;
10+
height: 33 * @dpi;
11+
overflow: hidden;
12+
}
13+
14+
.front {
15+
display: block;
16+
height: 50%;
17+
position: relative;
18+
19+
background-color: @green;
20+
21+
overflow: hidden;
22+
23+
.logo {
24+
display: inline-block;
25+
margin-left: 50%;
26+
position: relative;
27+
transform: translateX(-50%);
28+
top: 5.2 * @dpi;
29+
30+
padding: 0.4 * @dpi 0.9 * @dpi;
31+
32+
font-family: @font-family-serif-heading;
33+
font-size: 159px;
34+
font-variant-numeric: lining-nums;
35+
text-align: center;
36+
37+
color: @creme;
38+
39+
border: 28px solid @creme;
40+
}
41+
}
42+
43+
.back {
44+
display: block;
45+
height: 50%;
46+
overflow: hidden;
47+
position: relative;
48+
49+
transform: rotate(180deg);
50+
.box {
51+
position: absolute;
52+
left: 3.5 * @dpi;
53+
bottom: 2.3 * @dpi;
54+
max-width: 4 * @dpi;
55+
56+
font-size: 34px;
57+
font-family: @font-family-sans;
58+
line-height: 1.5;
59+
}
60+
61+
.hugLines {
62+
padding: 5px;
63+
padding-right: 5px;
64+
box-decoration-break: clone;
65+
}
66+
67+
.subhead > .hugLines {
68+
background-color: @green;
69+
color: @creme;
70+
}
71+
72+
.attribution > .hugLines {
73+
background-color: @creme;
74+
color: @font-color-dark;
75+
}
76+
77+
.map {
78+
display: block;
79+
width: 50%;
80+
height: 50%;
81+
82+
transform: scale(2);
83+
transform-origin: top left;
84+
85+
:global(.mapbox-improve-map) {
86+
display: none;
87+
}
88+
89+
// Text is copied to our own element
90+
:global(.mapboxgl-ctrl-attrib-inner),
91+
:global(.mapboxgl-ctrl-attrib mapboxgl-compact) {
92+
display: none;
93+
}
94+
}
95+
}
96+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import React from 'react';
2+
3+
import classNames from 'classnames';
4+
5+
import MainMap from '../MapPane/components/MainMap';
6+
7+
import stylesheet from './ToteBag.less';
8+
9+
export default function ToteBag(): JSX.Element {
10+
const containerRef = React.useRef<HTMLDivElement>(null);
11+
const [attributionText, setAttributionText] = React.useState<string>('');
12+
13+
React.useEffect(() => {
14+
const container = containerRef.current;
15+
if (!container) return;
16+
17+
const attributionEl = container.querySelector(
18+
'.mapboxgl-ctrl-attrib-inner'
19+
);
20+
if (!attributionEl) return;
21+
22+
const observer = new MutationObserver(() => {
23+
const text = (attributionEl as HTMLElement).innerText;
24+
const improveText =
25+
attributionEl.querySelector('.mapbox-improve-map')?.textContent || '';
26+
27+
if (text) {
28+
setAttributionText(text.replace(improveText, ''));
29+
}
30+
});
31+
32+
observer.observe(attributionEl, { childList: true });
33+
34+
return () => observer.disconnect();
35+
}, [containerRef]);
36+
37+
return (
38+
<div
39+
className={classNames(stylesheet.container)}
40+
id="render-content"
41+
ref={containerRef}
42+
>
43+
<div className={stylesheet.front}>
44+
<h1 className={stylesheet.logo}>1940s.nyc</h1>
45+
</div>
46+
<div className={stylesheet.back}>
47+
<div className={stylesheet.map}>
48+
<MainMap panOnClick={false} overlay="default-map" />
49+
</div>
50+
<div className={stylesheet.box}>
51+
<div className={stylesheet.subhead}>
52+
<span className={stylesheet.hugLines}>
53+
Street-level view of 1940s New York with thousands of your stories
54+
</span>
55+
</div>
56+
<div className={stylesheet.attribution}>
57+
<span className={stylesheet.hugLines}>Maps: {attributionText}</span>
58+
</div>
59+
</div>
60+
</div>
61+
</div>
62+
);
63+
}

frontend/src/shared/styles/font.less

Whitespace-only changes.

0 commit comments

Comments
 (0)