diff --git a/src/commons/navigationBar/subcomponents/SicpNavigationBar.tsx b/src/commons/navigationBar/subcomponents/SicpNavigationBar.tsx index 33046fde69..6dbc46bda3 100644 --- a/src/commons/navigationBar/subcomponents/SicpNavigationBar.tsx +++ b/src/commons/navigationBar/subcomponents/SicpNavigationBar.tsx @@ -3,8 +3,8 @@ import { IconNames } from '@blueprintjs/icons'; import * as React from 'react'; import { useHistory, useParams } from 'react-router'; import controlButton from 'src/commons/ControlButton'; +import { getNext, getPrev } from 'src/features/sicp/TableOfContentsHelper'; -import tocNavigation from '../../../features/sicp/data/toc-navigation.json'; import { TableOfContentsButton } from '../../../features/sicp/TableOfContentsButton'; import SicpToc from '../../../pages/sicp/subcomponents/SicpToc'; @@ -16,59 +16,33 @@ const SicpNavigationBar: React.FC = () => { const { section } = useParams<{ section: string }>(); const history = useHistory(); + const prev = getPrev(section); + const next = getNext(section); + const handleCloseToc = () => setIsTocOpen(false); + const handleOpenToc = () => setIsTocOpen(true); + const handleNavigation = (sect: string) => { + history.push('/interactive-sicp/' + sect); + }; // Button to open table of contents - const tocButton = React.useMemo(() => { - const handleOpenToc = () => setIsTocOpen(true); - return ; - }, []); + const tocButton = ; // Previous button only displayed when next page is valid. - const prevButton = React.useMemo(() => { - const sect = tocNavigation[section]; - if (!sect) { - return; - } - - const prev = sect['prev']; - if (!prev) { - return; - } - - const handlePrev = () => { - history.push('/interactive-sicp/' + prev); - }; - - return ( - prev &&
{controlButton('Previous', IconNames.ARROW_LEFT, handlePrev)}
- ); - }, [history, section]); + const prevButton = prev && ( +
+ {controlButton('Previous', IconNames.ARROW_LEFT, () => handleNavigation(prev))} +
+ ); // Next button only displayed when next page is valid. - const nextButton = React.useMemo(() => { - const sect = tocNavigation[section]; - if (!sect) { - return; - } - - const next = sect['next']; - if (!next) { - return; - } - - const handleNext = () => { - history.push('/interactive-sicp/' + next); - }; - - return ( - next && ( -
- {controlButton('Next', IconNames.ARROW_RIGHT, handleNext, { iconOnRight: true })} -
- ) - ); - }, [history, section]); + const nextButton = next && ( +
+ {controlButton('Next', IconNames.ARROW_RIGHT, () => handleNavigation(next), { + iconOnRight: true + })} +
+ ); const drawerProps = { onClose: handleCloseToc, diff --git a/src/commons/navigationBar/subcomponents/__tests__/__snapshots__/SicpNavigationBar.tsx.snap b/src/commons/navigationBar/subcomponents/__tests__/__snapshots__/SicpNavigationBar.tsx.snap index 2f460b98d6..4a36f17df5 100644 --- a/src/commons/navigationBar/subcomponents/__tests__/__snapshots__/SicpNavigationBar.tsx.snap +++ b/src/commons/navigationBar/subcomponents/__tests__/__snapshots__/SicpNavigationBar.tsx.snap @@ -8,7 +8,7 @@ exports[`Navbar renders correctly 1`] = `
- + Next
diff --git a/src/features/sicp/TableOfContentsHelper.ts b/src/features/sicp/TableOfContentsHelper.ts new file mode 100644 index 0000000000..1616d6947c --- /dev/null +++ b/src/features/sicp/TableOfContentsHelper.ts @@ -0,0 +1,13 @@ +import tocNavigation from './data/toc-navigation.json'; + +export const getNext = (section: string): string | undefined => { + const node = tocNavigation[section]; + + return node && node['next']; +}; + +export const getPrev = (section: string): string | undefined => { + const node = tocNavigation[section]; + + return node && node['prev']; +}; diff --git a/src/features/sicp/__tests__/TableOfContentsHelper.ts b/src/features/sicp/__tests__/TableOfContentsHelper.ts new file mode 100644 index 0000000000..486875b24a --- /dev/null +++ b/src/features/sicp/__tests__/TableOfContentsHelper.ts @@ -0,0 +1,31 @@ +import { getNext, getPrev } from '../TableOfContentsHelper'; + +const data = { + '1': { next: '2' }, + '2': { next: '3', prev: '1' }, + '3': { prev: '2' } +}; + +jest.mock('../data/toc-navigation.json', () => data); + +describe('Table of contents helper', () => { + test('generate next correctly', () => { + expect(getNext('1')).toBe('2'); + expect(getNext('2')).toBe('3'); + expect(getNext('3')).toBeUndefined(); + }); + + test('generate prev correctly', () => { + expect(getPrev('1')).toBeUndefined(); + expect(getPrev('2')).toBe('1'); + expect(getPrev('3')).toBe('2'); + }); + + test('handle invalid values correctly', () => { + expect(getNext('invalid')).toBeUndefined(); + expect(getNext('')).toBeUndefined(); + + expect(getPrev('invalid')).toBeUndefined(); + expect(getPrev('')).toBeUndefined(); + }); +}); diff --git a/src/pages/sicp/Sicp.tsx b/src/pages/sicp/Sicp.tsx index 74f2bbc9cd..6e2f9384e7 100644 --- a/src/pages/sicp/Sicp.tsx +++ b/src/pages/sicp/Sicp.tsx @@ -1,14 +1,15 @@ import 'katex/dist/katex.min.css'; -import { Classes, NonIdealState, Spinner } from '@blueprintjs/core'; +import { Button, Classes, NonIdealState, Spinner } from '@blueprintjs/core'; import { IconNames } from '@blueprintjs/icons'; import classNames from 'classnames'; import * as React from 'react'; import { useDispatch } from 'react-redux'; -import { RouteComponentProps, useParams } from 'react-router'; +import { RouteComponentProps, useHistory, useParams } from 'react-router'; import Constants from 'src/commons/utils/Constants'; import { resetWorkspace, toggleUsingSubst } from 'src/commons/workspace/WorkspaceActions'; import { parseArr, ParseJsonError } from 'src/features/sicp/parser/ParseJson'; +import { getNext, getPrev } from 'src/features/sicp/TableOfContentsHelper'; import SicpIndexPage from './subcomponents/SicpIndexPage'; @@ -66,6 +67,7 @@ const Sicp: React.FC = props => { const { section } = useParams<{ section: string }>(); const topRef = React.useRef(null); const refs = React.useRef({}); + const history = useHistory(); // Fetch json data React.useEffect(() => { @@ -137,6 +139,18 @@ const Sicp: React.FC = props => { dispatch(resetWorkspace('sicp')); dispatch(toggleUsingSubst(false, 'sicp')); }; + const handleNavigation = (sect: string | undefined) => { + history.push('/interactive-sicp/' + sect); + }; + + const navigationButtons = ( +
+ {getPrev(section) && ( + + )} + {getNext(section) && } +
+ ); return (
@@ -147,7 +161,10 @@ const Sicp: React.FC = props => { ) : section === 'index' ? ( ) : ( -
{data}
+
+ {data} + {navigationButtons} +
)}
diff --git a/src/styles/_sicp.scss b/src/styles/_sicp.scss index 1b1557100f..93d96ded6d 100644 --- a/src/styles/_sicp.scss +++ b/src/styles/_sicp.scss @@ -44,6 +44,23 @@ $sicp-background-color: #ffffff; height: fit-content; background-color: $sicp-background-color; + .sicp-navigation-buttons { + margin: 25px 0; + display: flex; + flex-flow: row nowrap; + justify-content: space-between; + + .bp3-button { + background-color: $cadet-color-3; + width: 80px; + padding: 10px 15px; + } + + .bp3-button:hover { + background-color: $cadet-color-1; + } + } + p { display: inline; }