Skip to content
Open
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
55 changes: 22 additions & 33 deletions src/PdfReader/reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,20 +31,10 @@ export function makePdfReducer(
// navigating to a different page in the same resource)
const shouldResetResource = state.resourceIndex !== index;

// check if there is a `?startPage` in the href of the resource we are navigating to
const href = manifest.readingOrder[index].href;
const startPage = getStartPageFromHref(href);
// only go to the start page if we don't have another valid page we are navigating to
// instead.
const isNavigatingToEnd = page === -1;
const requestedPageIsBeforeStartPage = startPage && page < startPage;
const pageNumberToNavigateTo =
!isNavigatingToEnd && requestedPageIsBeforeStartPage ? startPage : page;

const newState = {
...state,
resourceIndex: index,
pageNumber: pageNumberToNavigateTo,
pageNumber: page,
};
if (shouldResetResource) {
return {
Expand Down Expand Up @@ -113,11 +103,7 @@ export function makePdfReducer(
*/
// do nothing if we have not parsed the number of pages yet.
if (!state.numPages) return state;
const atStartOfResource = isStartOfResource(
state.pageNumber,
args.manifest.readingOrder[state.resourceIndex].href
);

const atStartOfResource = state.pageNumber === 1;
const atStartOfBook = state.resourceIndex === 0;
if (atStartOfResource) {
if (atStartOfBook) return state;
Expand All @@ -126,8 +112,11 @@ export function makePdfReducer(
...goToLocation(state.resourceIndex - 1, -1),
};
}
// go to prev page
return goToLocation(state.resourceIndex, state.pageNumber - 1);
// go to prev page, allowing navigation below startPage within the resource
return goToLocation(
state.resourceIndex,
Math.max(1, state.pageNumber - 1)
);
}

case 'GO_TO_HREF': {
Expand Down Expand Up @@ -161,15 +150,24 @@ export function makePdfReducer(

// called when the resource has been parsed by react-pdf
// and we know the number of pages
case 'PDF_PARSED':
case 'PDF_PARSED': {
const { numPages } = action;
const { pageNumber: currentPage, resourceIndex } = state;

const currentHref = manifest.readingOrder[resourceIndex]?.href;
const startPage = getStartPageFromHref(currentHref) ?? 0;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

startPage should default to 1 to be consistent with the rest of this reducer.


// 1. If -1, go to the end.
// 2. Otherwise, ensure we don't fall below startPage.
const pageNumber =
currentPage === -1 ? numPages : Math.max(currentPage, startPage);

return {
...state,
numPages: action.numPages,
// if the state.pageNumber is -1, we know to navigate to the
// end of the PDF that was just parsed
pageNumber:
state.pageNumber === -1 ? action.numPages : state.pageNumber,
numPages,
pageNumber,
};
}

case 'PDF_LOAD_ERROR':
return {
Expand Down Expand Up @@ -255,12 +253,3 @@ function handleInvalidTransition(state: PdfState, action: PdfReaderAction) {
);
return state;
}

/**
* Checks if we are at the start of the resource, taking into account the `?startPage`
* query param.
*/
function isStartOfResource(pageNumber: number, resourceHref: string) {
const startPage = getStartPageFromHref(resourceHref);
return pageNumber === (startPage ?? 1);
}
84 changes: 84 additions & 0 deletions tests/PdfReducer.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { makePdfReducer } from '../src/PdfReader/reducer';
import { PdfReaderArguments, PdfState } from '../src/PdfReader/types';
import { DEFAULT_FIT_MODE, DEFAULT_SETTINGS } from '../src/constants';

function makeArgs(href: string): PdfReaderArguments {
return {
manifest: {
metadata: { title: 'Test' },
readingOrder: [{ href, type: 'application/pdf' }],
},
} as unknown as PdfReaderArguments;
}

const baseState: PdfState = {
state: 'ACTIVE',
settings: DEFAULT_SETTINGS,
resourceIndex: 0,
resource: null,
pageNumber: 1,
numPages: null,
scale: 1,
pdfWidth: 0,
pdfHeight: 0,
pageHeight: undefined,
pageWidth: undefined,
atStart: true,
atEnd: false,
rendered: false,
fitMode: DEFAULT_FIT_MODE,
rotation: 0,
};

describe('makePdfReducer — PDF_PARSED', () => {
it('keeps page 1 when no start query param is present', () => {
const args = makeArgs('https://example.com/doc.pdf');
const reducer = makePdfReducer(args);
const state = reducer(baseState, { type: 'PDF_PARSED', numPages: 100 });
expect(state.pageNumber).toBe(1);
});

it('jumps to start on initial load when pageNumber is below start', () => {
const args = makeArgs('https://example.com/doc.pdf?start=15');
const reducer = makePdfReducer(args);
const state = reducer(baseState, { type: 'PDF_PARSED', numPages: 100 });
expect(state.pageNumber).toBe(15);
});

it('does not override pageNumber when user has already navigated past start', () => {
const args = makeArgs('https://example.com/doc.pdf?start=15');
const reducer = makePdfReducer(args);
const navigatedState = { ...baseState, pageNumber: 20 };
const state = reducer(navigatedState, {
type: 'PDF_PARSED',
numPages: 100,
});
expect(state.pageNumber).toBe(20);
});

it('navigates to last page when pageNumber is -1', () => {
const args = makeArgs('https://example.com/doc.pdf');
const reducer = makePdfReducer(args);
const endState = { ...baseState, pageNumber: -1 };
const state = reducer(endState, { type: 'PDF_PARSED', numPages: 100 });
expect(state.pageNumber).toBe(100);
});
});

describe('makePdfReducer — GO_BACKWARD', () => {
it('allows navigating back below start page within the same resource', () => {
const args = makeArgs('https://example.com/doc.pdf?start=5');
const reducer = makePdfReducer(args);
const onStartPage = { ...baseState, pageNumber: 5, numPages: 100 };
const state = reducer(onStartPage, { type: 'GO_BACKWARD' });
expect(state.pageNumber).toBe(4);
});

it('does not go below page 1', () => {
const args = makeArgs('https://example.com/doc.pdf');
const reducer = makePdfReducer(args);
const onFirstPage = { ...baseState, pageNumber: 1, numPages: 100 };
const state = reducer(onFirstPage, { type: 'GO_BACKWARD' });
expect(state.pageNumber).toBe(1);
});
});
Loading