Skip to content

Commit

Permalink
Direct links to narrative sections (#418)
Browse files Browse the repository at this point in the history
  • Loading branch information
macfarlandian committed May 5, 2021
1 parent 5e01bf6 commit 85c4003
Show file tree
Hide file tree
Showing 23 changed files with 609 additions and 152 deletions.
18 changes: 18 additions & 0 deletions spotlight-client/@types/smoothscroll-polyfill.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Recidiviz - a data platform for criminal justice reform
// Copyright (C) 2021 Recidiviz, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// =============================================================================

declare module "smoothscroll-polyfill";
3 changes: 2 additions & 1 deletion spotlight-client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@
"mobx": "^6.0.4",
"mobx-react-lite": "^3.0.1",
"mobx-utils": "^6.0.1",
"polished": "^4.0.5",
"polished": "^4.1.2",
"pupa": "^2.1.1",
"qs": "^6.9.4",
"react": "^17.0.0",
Expand All @@ -86,6 +86,7 @@
"react-spring": "^8.0.27",
"react-stickyfill": "^0.2.5",
"semiotic": "^1.20.6",
"smoothscroll-polyfill": "^0.4.4",
"string-strip-html": "^8.2.2",
"styled-components": "^5.2.1",
"styled-reset": "^4.3.3",
Expand Down
4 changes: 3 additions & 1 deletion spotlight-client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,9 @@ const App: React.FC = () => {
<PageHome path="/" />
<PassThroughPage path="/:tenantId">
<PageTenant path="/" />
<PageNarrative path={`/${NarrativesSlug}/:narrativeTypeId`} />
<PageNarrative
path={`/${NarrativesSlug}/:narrativeTypeId/*sectionNumber`}
/>
<PageNotFound path="/*" />
</PassThroughPage>
</Router>
Expand Down
84 changes: 84 additions & 0 deletions spotlight-client/src/DataStore/TenantStore.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,3 +112,87 @@ test("lock tenant if set from current domain", () => {
expect(tenantStore.locked).toBe(true);
});
});

test("default section number", () => {
const { tenantStore } = getDataStore();

reactImmediately(() => {
expect(tenantStore.currentSectionNumber).toBeUndefined();
});

runInAction(() => {
tenantStore.currentTenantId = "US_PA";
tenantStore.currentNarrativeTypeId = "Prison";
});

reactImmediately(() => {
expect(tenantStore.currentSectionNumber).toBe(1);
});

runInAction(() => {
tenantStore.currentNarrativeTypeId = undefined;
});

reactImmediately(() => {
expect(tenantStore.currentSectionNumber).toBeUndefined();
});
});

test("can set current section number", () => {
const { tenantStore } = getDataStore();

runInAction(() => {
tenantStore.currentTenantId = "US_PA";
tenantStore.currentNarrativeTypeId = "Prison";
tenantStore.currentSectionNumber = 2;
});

reactImmediately(() => {
expect(tenantStore.currentSectionNumber).toBe(2);
});
});

test("section number is validated against current narrative", () => {
const { tenantStore } = getDataStore();

runInAction(() => {
tenantStore.currentTenantId = "US_PA";
tenantStore.currentNarrativeTypeId = "Prison";
tenantStore.currentSectionNumber = 3;
});

// cannot be cleared
runInAction(() => {
tenantStore.currentSectionNumber = undefined;
});

reactImmediately(() => {
expect(tenantStore.currentSectionNumber).toBe(1);
});

// cannot be nonsense
runInAction(() => {
tenantStore.currentSectionNumber = 0;
});

reactImmediately(() => {
expect(tenantStore.currentSectionNumber).toBe(1);
});

runInAction(() => {
tenantStore.currentSectionNumber = -5;
});

reactImmediately(() => {
expect(tenantStore.currentSectionNumber).toBe(1);
});

// cannot be higher than the number of sections
runInAction(() => {
tenantStore.currentSectionNumber = 99;
});

reactImmediately(() => {
expect(tenantStore.currentSectionNumber).toBe(4);
});
});
30 changes: 30 additions & 0 deletions spotlight-client/src/DataStore/TenantStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// =============================================================================

import clamp from "lodash/clamp";
import { intercept, makeAutoObservable } from "mobx";
import { ERROR_MESSAGES } from "../constants";
import {
Expand All @@ -33,6 +34,8 @@ export default class TenantStore {

currentTenantId?: TenantId;

private validatedSectionNumber?: number;

readonly locked: boolean = false;

rootStore: RootStore;
Expand Down Expand Up @@ -93,4 +96,31 @@ export default class TenantStore {

return currentTenant.racialDisparitiesNarrative;
}

/**
* If there is a current narrative (all narratives have sections),
* this will always be a number between 1 and the number of sections
* to be displayed on the narrative page (which includes an introduction).
*/
get currentSectionNumber(): number | undefined {
if (!this.currentNarrative) return undefined;
return this.validatedSectionNumber || 1;
}

set currentSectionNumber(value: number | undefined) {
let validatedValue;
if (this.currentNarrative) {
validatedValue = 1;
if (value) {
validatedValue = clamp(
value,
1,
// +1 for the intro
this.currentNarrative.sections.length + 1
);
}
}

this.validatedSectionNumber = validatedValue;
}
}
17 changes: 16 additions & 1 deletion spotlight-client/src/GlobalStyles/GlobalStyles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,27 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// =============================================================================

import { rem } from "polished";
import React from "react";
import { Helmet } from "react-helmet-async";
import { createGlobalStyle } from "styled-components/macro";
import { createGlobalStyle, css } from "styled-components/macro";
import reset from "styled-reset";
import { NAV_BAR_HEIGHT } from "../constants";
import { colors, typefaces } from "../UiLibrary";

const scrollSnapStyles = css`
scroll-padding-top: ${rem(NAV_BAR_HEIGHT)};
scroll-snap-type: y proximity;
`;

const BaseStyles = createGlobalStyle`
${reset}
html {
box-sizing: border-box;
font-family: ${typefaces.body};
/* most browsers support full-page scroll snapping with this element */
${scrollSnapStyles}
*, *:before, *:after {
box-sizing: inherit;
Expand All @@ -37,6 +46,12 @@ const BaseStyles = createGlobalStyle`
body {
background-color: ${colors.background};
color: ${colors.text};
/*
Safari only supports full-page scroll snapping with this element for some reason;
see https://stackoverflow.com/a/60470570.
Setting it on both elements does not appear to create any conflicts
*/
${scrollSnapStyles}
}
strong {
Expand Down
Loading

0 comments on commit 85c4003

Please sign in to comment.