@readium/navigator 1.3.4 → 2.0.0-beta.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/dist/index.js +3974 -2928
  2. package/dist/index.umd.cjs +16 -16
  3. package/package.json +10 -9
  4. package/src/Navigator.ts +11 -0
  5. package/src/epub/EpubNavigator.ts +250 -24
  6. package/src/epub/css/Properties.ts +396 -0
  7. package/src/epub/css/ReadiumCSS.ts +339 -0
  8. package/src/epub/css/index.ts +2 -0
  9. package/src/epub/frame/FrameBlobBuilder.ts +59 -9
  10. package/src/epub/frame/FrameManager.ts +23 -1
  11. package/src/epub/frame/FramePoolManager.ts +62 -4
  12. package/src/epub/fxl/FXLFramePoolManager.ts +23 -16
  13. package/src/epub/index.ts +3 -1
  14. package/src/epub/preferences/EpubDefaults.ts +165 -0
  15. package/src/epub/preferences/EpubPreferences.ts +192 -0
  16. package/src/epub/preferences/EpubPreferencesEditor.ts +534 -0
  17. package/src/epub/preferences/EpubSettings.ts +239 -0
  18. package/src/epub/preferences/guards.ts +86 -0
  19. package/src/epub/preferences/index.ts +4 -0
  20. package/src/helpers/dimensions.ts +13 -0
  21. package/src/helpers/index.ts +1 -0
  22. package/src/helpers/lineLength.ts +241 -0
  23. package/src/helpers/sML.ts +25 -3
  24. package/src/index.ts +2 -1
  25. package/src/preferences/Configurable.ts +16 -0
  26. package/src/preferences/Preference.ts +272 -0
  27. package/src/preferences/PreferencesEditor.ts +6 -0
  28. package/src/preferences/Types.ts +38 -0
  29. package/src/preferences/index.ts +4 -0
  30. package/types/src/Navigator.d.ts +9 -0
  31. package/types/src/epub/EpubNavigator.d.ts +34 -4
  32. package/types/src/epub/css/Properties.d.ts +183 -0
  33. package/types/src/epub/css/ReadiumCSS.d.ts +31 -0
  34. package/types/src/epub/css/index.d.ts +2 -0
  35. package/types/src/epub/frame/FrameBlobBuilder.d.ts +5 -1
  36. package/types/src/epub/frame/FrameManager.d.ts +4 -0
  37. package/types/src/epub/frame/FramePoolManager.d.ts +8 -1
  38. package/types/src/epub/fxl/FXLFramePoolManager.d.ts +4 -4
  39. package/types/src/epub/index.d.ts +2 -0
  40. package/types/src/epub/preferences/EpubDefaults.d.ts +86 -0
  41. package/types/src/epub/preferences/EpubPreferences.d.ts +90 -0
  42. package/types/src/epub/preferences/EpubPreferencesEditor.d.ts +55 -0
  43. package/types/src/epub/preferences/EpubSettings.d.ts +89 -0
  44. package/types/src/epub/preferences/guards.d.ts +9 -0
  45. package/types/src/epub/preferences/index.d.ts +4 -0
  46. package/types/src/helpers/dimensions.d.ts +7 -0
  47. package/types/src/helpers/index.d.ts +1 -0
  48. package/types/src/helpers/lineLength.d.ts +54 -0
  49. package/types/src/helpers/sML.d.ts +6 -1
  50. package/types/src/index.d.ts +1 -0
  51. package/types/src/preferences/Configurable.d.ts +13 -0
  52. package/types/src/preferences/Preference.d.ts +117 -0
  53. package/types/src/preferences/PreferencesEditor.d.ts +5 -0
  54. package/types/src/preferences/PreferencesSerializer.d.ts +5 -0
  55. package/types/src/preferences/Types.d.ts +23 -0
  56. package/types/src/preferences/index.d.ts +4 -0
  57. package/LICENSE +0 -28
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@readium/navigator",
3
- "version": "1.3.4",
3
+ "version": "2.0.0-beta.10",
4
4
  "type": "module",
5
5
  "description": "Next generation SDK for publications in Web Apps",
6
6
  "author": "readium",
@@ -44,20 +44,21 @@
44
44
  "engines": {
45
45
  "node": ">=18"
46
46
  },
47
+ "scripts": {
48
+ "build": "tsc && vite build"
49
+ },
47
50
  "devDependencies": {
48
51
  "@laynezh/vite-plugin-lib-assets": "^0.5.25",
52
+ "@readium/css": ">=2.0.0-beta.18",
53
+ "@readium/navigator-html-injectables": "workspace:*",
54
+ "@readium/shared": "workspace:*",
49
55
  "@types/path-browserify": "^1.0.3",
50
56
  "css-selector-generator": "^3.6.9",
51
57
  "path-browserify": "^1.0.1",
52
- "@readium/css": "^1.1.0",
53
58
  "tslib": "^2.8.1",
54
59
  "typescript": "^5.6.3",
55
60
  "typescript-plugin-css-modules": "^5.1.0",
56
- "vite": "^4.5.5",
57
- "@readium/navigator-html-injectables": "1.3.2",
58
- "@readium/shared": "1.2.1"
59
- },
60
- "scripts": {
61
- "build": "tsc && vite build"
61
+ "user-agent-data-types": "^0.4.2",
62
+ "vite": "^4.5.5"
62
63
  }
63
- }
64
+ }
package/src/Navigator.ts CHANGED
@@ -2,6 +2,17 @@ import { Link, Locator, Publication, ReadingProgression } from "@readium/shared"
2
2
 
3
3
  type cbb = (ok: boolean) => void;
4
4
 
5
+ export interface ProgressionRange {
6
+ start: number;
7
+ end: number;
8
+ }
9
+
10
+ export interface VisualNavigatorViewport {
11
+ readingOrder: string[]; // Array of href strings for visible resources
12
+ progressions: Map<string, ProgressionRange>; // Map from href to visible scroll progression range
13
+ positions: number[] | null; // Range of visible positions
14
+ }
15
+
5
16
  export abstract class Navigator {
6
17
  abstract get publication(): Publication; // Publication rendered by this navigator.
7
18
  abstract get currentLocator(): Locator; // Current position (detailed) in the publication. Can be used to save a bookmark to the current position.
@@ -1,5 +1,5 @@
1
1
  import { EPUBLayout, Link, Locator, Publication, ReadingProgression } from "@readium/shared";
2
- import { VisualNavigator } from "../";
2
+ import { Configurable, ConfigurablePreferences, ConfigurableSettings, LineLengths, VisualNavigator, VisualNavigatorViewport, ProgressionRange } from "../";
3
3
  import { FramePoolManager } from "./frame/FramePoolManager";
4
4
  import { FXLFramePoolManager } from "./fxl/FXLFramePoolManager";
5
5
  import { CommsEventKey, FXLModules, ModuleLibrary, ModuleName, ReflowableModules } from "@readium/navigator-html-injectables";
@@ -7,9 +7,21 @@ import { BasicTextSelection, FrameClickEvent } from "@readium/navigator-html-inj
7
7
  import * as path from "path-browserify";
8
8
  import { FXLFrameManager } from "./fxl/FXLFrameManager";
9
9
  import { FrameManager } from "./frame/FrameManager";
10
+ import { IEpubPreferences, EpubPreferences } from "./preferences/EpubPreferences";
11
+ import { IEpubDefaults, EpubDefaults } from "./preferences/EpubDefaults";
12
+ import { EpubSettings } from "./preferences";
13
+ import { EpubPreferencesEditor } from "./preferences/EpubPreferencesEditor";
14
+ import { ReadiumCSS } from "./css/ReadiumCSS";
15
+ import { RSProperties, UserProperties } from "./css/Properties";
16
+ import { getContentWidth } from "../helpers/dimensions";
10
17
 
11
18
  export type ManagerEventKey = "zoom";
12
19
 
20
+ export interface EpubNavigatorConfiguration {
21
+ preferences: IEpubPreferences;
22
+ defaults: IEpubDefaults;
23
+ }
24
+
13
25
  export interface EpubNavigatorListeners {
14
26
  frameLoaded: (wnd: Window) => void;
15
27
  positionChanged: (locator: Locator) => void;
@@ -35,7 +47,7 @@ const defaultListeners = (listeners: EpubNavigatorListeners): EpubNavigatorListe
35
47
  textSelected: listeners.textSelected || (() => {})
36
48
  })
37
49
 
38
- export class EpubNavigator extends VisualNavigator {
50
+ export class EpubNavigator extends VisualNavigator implements Configurable<ConfigurableSettings, ConfigurablePreferences> {
39
51
  private readonly pub: Publication;
40
52
  private readonly container: HTMLElement;
41
53
  private readonly listeners: EpubNavigatorListeners;
@@ -46,16 +58,61 @@ export class EpubNavigator extends VisualNavigator {
46
58
  private currentProgression: ReadingProgression;
47
59
  public readonly layout: EPUBLayout;
48
60
 
49
- constructor(container: HTMLElement, pub: Publication, listeners: EpubNavigatorListeners, positions: Locator[] = [], initialPosition: Locator | undefined = undefined) {
61
+ private _preferences: EpubPreferences;
62
+ private _defaults: EpubDefaults;
63
+ private _settings: EpubSettings;
64
+ private _css: ReadiumCSS;
65
+ private _preferencesEditor: EpubPreferencesEditor | null = null;
66
+
67
+ private resizeObserver: ResizeObserver;
68
+
69
+ private reflowViewport: VisualNavigatorViewport = {
70
+ readingOrder: [],
71
+ progressions: new Map(),
72
+ positions: null
73
+ };
74
+
75
+ constructor(container: HTMLElement, pub: Publication, listeners: EpubNavigatorListeners, positions: Locator[] = [], initialPosition: Locator | undefined = undefined, configuration: EpubNavigatorConfiguration = { preferences: {}, defaults: {} }) {
50
76
  super();
51
77
  this.pub = pub;
52
78
  this.layout = EpubNavigator.determineLayout(pub);
53
- this.currentProgression = pub.metadata.effectiveReadingProgression;
54
79
  this.container = container;
55
80
  this.listeners = defaultListeners(listeners);
56
81
  this.currentLocation = initialPosition!;
57
82
  if (positions.length)
58
83
  this.positions = positions;
84
+
85
+ this._preferences = new EpubPreferences(configuration.preferences);
86
+ this._defaults = new EpubDefaults(configuration.defaults);
87
+ this._settings = new EpubSettings(this._preferences, this._defaults);
88
+ this._css = new ReadiumCSS({
89
+ rsProperties: new RSProperties({}),
90
+ userProperties: new UserProperties({}),
91
+ lineLengths: new LineLengths({
92
+ optimalChars: this._settings.optimalLineLength,
93
+ minChars: this._settings.minimalLineLength,
94
+ maxChars: this._settings.maximalLineLength,
95
+ pageGutter: this._settings.pageGutter,
96
+ fontFace: this._settings.fontFamily,
97
+ letterSpacing: this._settings.letterSpacing,
98
+ wordSpacing: this._settings.wordSpacing,
99
+ // sample: this.pub.metadata.description
100
+ }),
101
+ container: container,
102
+ constraint: this._settings.constraint
103
+ });
104
+
105
+ this.currentProgression = this.layout === EPUBLayout.reflowable
106
+ ? (this._settings.scroll
107
+ ? ReadingProgression.ttb
108
+ : pub.metadata.effectiveReadingProgression)
109
+ : pub.metadata.effectiveReadingProgression;
110
+
111
+ // We use a resizeObserver cos’ the container parent may not be the width of
112
+ // the document/window e.g. app using a docking system with left and right panels.
113
+ // If we observe this.container, that won’t obviously work since we set its width.
114
+ this.resizeObserver = new ResizeObserver(() => this.ownerWindow.requestAnimationFrame(async () => await this.resizeHandler()));
115
+ this.resizeObserver.observe(this.container.parentElement || document.documentElement);
59
116
  }
60
117
 
61
118
  public static determineLayout(pub: Publication): EPUBLayout {
@@ -79,13 +136,139 @@ export class EpubNavigator extends VisualNavigator {
79
136
  this.framePool.listener = (key: CommsEventKey | ManagerEventKey, data: unknown) => {
80
137
  this.eventListener(key, data);
81
138
  }
82
- } else
83
- this.framePool = new FramePoolManager(this.container, this.positions);
139
+ } else {
140
+ await this.updateCSS(false);
141
+ const cssProperties = this.compileCSSProperties(this._css);
142
+ this.framePool = new FramePoolManager(this.container, this.positions, cssProperties);
143
+ }
144
+
84
145
  if(this.currentLocation === undefined)
85
146
  this.currentLocation = this.positions[0];
147
+
148
+ await this.resizeHandler();
86
149
  await this.apply();
87
150
  }
88
151
 
152
+ public get settings(): Readonly<EpubSettings> {
153
+ if (this.layout === EPUBLayout.fixed) {
154
+ return Object.freeze({ ...this._settings });
155
+ } else {
156
+ // Given all the nasty issues moving auto-pagination to EpubSettings creates
157
+ // Especially as it’s tied to ReadiumCSS in the first place and could be
158
+ // problematic if you intend to use something else,
159
+ // we return the properties with columnCount overridden
160
+ const columnCount = this._css.userProperties.colCount || this._css.rsProperties.colCount || this._settings.columnCount;
161
+ return Object.freeze({ ...this._settings, columnCount: columnCount });
162
+ }
163
+ }
164
+
165
+ public get preferencesEditor() {
166
+ if (this._preferencesEditor === null) {
167
+ // Note: we pass this.settings instead of this._settings to ensure the columnCount is correct
168
+ this._preferencesEditor = new EpubPreferencesEditor(this._preferences, this.settings, this.pub.metadata);
169
+ }
170
+ return this._preferencesEditor;
171
+ }
172
+
173
+ public async submitPreferences(preferences: EpubPreferences) {
174
+ this._preferences = this._preferences.merging(preferences) as EpubPreferences;
175
+ await this.applyPreferences();
176
+ }
177
+
178
+ private async applyPreferences() {
179
+ const oldSettings = this._settings;
180
+ this._settings = new EpubSettings(this._preferences, this._defaults);
181
+
182
+ if (this._preferencesEditor !== null) {
183
+ // Note: we pass this.settings instead of this._settings to ensure the columnCount is correct
184
+ this._preferencesEditor = new EpubPreferencesEditor(this._preferences, this.settings, this.pub.metadata);
185
+ }
186
+
187
+ if (this.layout === EPUBLayout.fixed) {
188
+ this.handleFXLPrefs(oldSettings, this._settings);
189
+ } else {
190
+ await this.updateCSS(true);
191
+ }
192
+ }
193
+
194
+ // TODO: fit, etc.
195
+ private handleFXLPrefs(from: EpubSettings, to: EpubSettings) {
196
+ if (from.columnCount !== to.columnCount) {
197
+ (this.framePool as FXLFramePoolManager).setPerPage(to.columnCount);
198
+ }
199
+ }
200
+
201
+ private async updateCSS(commit: boolean) {
202
+ this._css.update(this._settings);
203
+
204
+ if (commit) await this.commitCSS(this._css);
205
+ };
206
+
207
+ private compileCSSProperties(css: ReadiumCSS) {
208
+ const properties: { [key: string]: string } = {};
209
+
210
+ for (const [key, value] of Object.entries(css.rsProperties.toCSSProperties())) {
211
+ properties[key] = value;
212
+ }
213
+
214
+ for (const [key, value] of Object.entries(css.userProperties.toCSSProperties())) {
215
+ properties[key] = value;
216
+ }
217
+
218
+ return properties;
219
+ }
220
+
221
+ private async commitCSS(css: ReadiumCSS) {
222
+ // Since we’re updating the CSS properties in injectables by removing
223
+ // the existing properties that are not inside this object first,
224
+ // then adding all from it, we don’t compare the previous properties here
225
+ const properties = this.compileCSSProperties(css);
226
+
227
+ (this.framePool as FramePoolManager).setCSSProperties(properties);
228
+
229
+ if (
230
+ this._css.userProperties.view === "paged" &&
231
+ this.readingProgression === ReadingProgression.ttb
232
+ ) {
233
+ await this.setReadingProgression(this.pub.metadata.effectiveReadingProgression);
234
+ } else if (
235
+ this._css.userProperties.view === "scroll" &&
236
+ (this.readingProgression === ReadingProgression.ltr || this.readingProgression === ReadingProgression.rtl)
237
+ ) {
238
+ await this.setReadingProgression(ReadingProgression.ttb);
239
+ }
240
+
241
+ this._css.setContainerWidth();
242
+ }
243
+
244
+ async resizeHandler() {
245
+ // We check the parentElement cos we want to remove constraint from the container
246
+ // and the container may not be the entire width of the document/window
247
+ const parentEl = this.container.parentElement || document.documentElement;
248
+
249
+ if (this.layout === EPUBLayout.fixed) {
250
+ this.container.style.width = `${ getContentWidth(parentEl) - this._settings.constraint }px`;
251
+ (this.framePool as FXLFramePoolManager).resizeHandler();
252
+ } else {
253
+ // for reflow ReadiumCSS gets the width from columns + line-lengths
254
+ // but we need to check whether colCount has changed to commit new CSS
255
+ const oldColCount = this._css.userProperties.colCount;
256
+ const oldLineLength = this._css.userProperties.lineLength;
257
+ this._css.resizeHandler();
258
+ if (
259
+ this._css.userProperties.view !== "scroll" &&
260
+ oldColCount !== this._css.userProperties.colCount ||
261
+ oldLineLength !== this._css.userProperties.lineLength
262
+ ) {
263
+ await this.commitCSS(this._css);
264
+ }
265
+ }
266
+ }
267
+
268
+ get ownerWindow() {
269
+ return this.container.ownerDocument.defaultView || window;
270
+ }
271
+
89
272
  /**
90
273
  * Exposed to the public to compensate for lack of implemented readium conveniences
91
274
  * TODO remove when settings management is incorporated
@@ -217,7 +400,7 @@ export class EpubNavigator extends VisualNavigator {
217
400
  this.listeners.zoom(data as number);
218
401
  break;
219
402
  case "progress":
220
- this.syncLocation(data as { progress: number, reference: number });
403
+ this.syncLocation(data as ProgressionRange);
221
404
  break;
222
405
  case "log":
223
406
  console.log(this._cframes[0]?.source?.split("/")[3], ...(data as any[]));
@@ -275,7 +458,7 @@ export class EpubNavigator extends VisualNavigator {
275
458
 
276
459
  if(this.layout === EPUBLayout.fixed) {
277
460
  const p = this.framePool as FXLFramePoolManager;
278
- const old = p.currentNumbers[0];
461
+ const old = p.viewport.positions![0];
279
462
  if(relative === 1) {
280
463
  if(!p.next(p.perPage)) return false;
281
464
  } else if(relative === -1) {
@@ -284,7 +467,7 @@ export class EpubNavigator extends VisualNavigator {
284
467
  throw Error("Invalid relative value for FXL");
285
468
 
286
469
  // Apply change
287
- const neW = p.currentNumbers[0]
470
+ const neW = p.viewport.positions![0]
288
471
  if(old > neW)
289
472
  for (let j = this.positions.length - 1; j >= 0; j--) {
290
473
  if(this.positions[j].href === this.pub.readingOrder.items[neW-1].href) {
@@ -337,10 +520,10 @@ export class EpubNavigator extends VisualNavigator {
337
520
  return true;
338
521
  }
339
522
 
340
- private findLastPositionInProgressionRange(positions: Locator[], range: number[]): Locator | undefined {
523
+ private findLastPositionInProgressionRange(positions: Locator[], range: ProgressionRange): Locator | undefined {
341
524
  const match = positions.findLastIndex((p) => {
342
525
  const pr = p.locations.progression;
343
- if (pr && pr > Math.min(...range) && pr <= Math.max(...range)) {
526
+ if (pr && pr > range.start && pr <= range.end) {
344
527
  return true;
345
528
  } else {
346
529
  return false;
@@ -349,7 +532,7 @@ export class EpubNavigator extends VisualNavigator {
349
532
  return match !== -1 ? positions[match] : undefined;
350
533
  }
351
534
 
352
- private findNearestPositions(fromProgression: { progress: number, reference: number }): { first: Locator, last: Locator | undefined } {
535
+ private findNearestPositions(fromProgression: ProgressionRange): { first: Locator, last: Locator | undefined } {
353
536
  // TODO replace with locator service
354
537
  const potentialPositions = this.positions.filter(
355
538
  (p) => p.href === this.currentLocation.href
@@ -361,13 +544,12 @@ export class EpubNavigator extends VisualNavigator {
361
544
  // smaller than or equal to the requested progression.
362
545
  potentialPositions.some((p, idx) => {
363
546
  const pr = p.locations.progression ?? 0;
364
- if (fromProgression.progress <= pr) {
547
+ if (fromProgression.start <= pr) {
365
548
  first = p;
366
549
 
367
550
  // If there’s a match, check the last in view, from the next progression
368
551
  const nextPositions = potentialPositions.splice(idx + 1, potentialPositions.length);
369
- const range = [fromProgression.progress, fromProgression.progress + fromProgression.reference];
370
- last = this.findLastPositionInProgressionRange(nextPositions, range);
552
+ last = this.findLastPositionInProgressionRange(nextPositions, fromProgression);
371
553
 
372
554
  return true;
373
555
  }
@@ -376,12 +558,36 @@ export class EpubNavigator extends VisualNavigator {
376
558
  return { first: first, last: last }
377
559
  }
378
560
 
379
- private async syncLocation(iframeProgress: { progress: number, reference: number }) {
380
- const nearestPositions = this.findNearestPositions(iframeProgress)
561
+ private updateViewport(progression: ProgressionRange) {
562
+ this.reflowViewport.readingOrder = [];
563
+ this.reflowViewport.progressions.clear();
564
+ this.reflowViewport.positions = null;
565
+
566
+ // Use the current position's href
567
+ if (this.currentLocation) {
568
+ this.reflowViewport.readingOrder.push(this.currentLocation.href);
569
+ this.reflowViewport.progressions.set(this.currentLocation.href, progression);
570
+
571
+ if (this.currentLocation.locations?.position !== undefined) {
572
+ this.reflowViewport.positions = [this.currentLocation.locations.position];
573
+ if (this.lastLocationInView?.locations?.position !== undefined) {
574
+ this.reflowViewport.positions.push(this.lastLocationInView.locations.position);
575
+ }
576
+ }
577
+ }
578
+ }
579
+
580
+ private async syncLocation(iframeProgress: ProgressionRange) {
581
+ const progression = iframeProgress;
582
+
583
+ const nearestPositions = this.findNearestPositions(progression);
584
+
381
585
  this.currentLocation = nearestPositions.first.copyWithLocations({
382
- progression: iframeProgress.progress // Most accurate progression in resource
586
+ progression: progression.start
383
587
  });
588
+
384
589
  this.lastLocationInView = nearestPositions.last;
590
+ this.updateViewport(progression);
385
591
  this.listeners.positionChanged(this.currentLocation);
386
592
  await this.framePool.update(this.pub, this.currentLocation, this.determineModules());
387
593
  }
@@ -427,12 +633,32 @@ export class EpubNavigator extends VisualNavigator {
427
633
  return this.currentLocation;
428
634
  }
429
635
 
430
- // Starting and ending position currently showing in the reader
431
- get currentPositionNumbers(): number[] {
432
- if(this.layout === EPUBLayout.fixed)
433
- return (this.framePool as FXLFramePoolManager).currentNumbers;
636
+ get viewport(): VisualNavigatorViewport {
637
+ return this.layout === EPUBLayout.fixed
638
+ ? (this.framePool as FXLFramePoolManager).viewport
639
+ : this.reflowViewport;
640
+ }
434
641
 
435
- return [this.currentLocator?.locations.position ?? 0, ...(this.lastLocationInView?.locations.position ? [this.lastLocationInView.locations.position] : [])];
642
+ get isScrollStart(): boolean {
643
+ const firstHref = this.viewport.readingOrder[0];
644
+ const progression = this.viewport.progressions.get(firstHref);
645
+ return progression?.start === 0;
646
+ }
647
+
648
+ get isScrollEnd(): boolean {
649
+ const lastHref = this.viewport.readingOrder[this.viewport.readingOrder.length - 1];
650
+ const progression = this.viewport.progressions.get(lastHref);
651
+ return progression?.end === 1;
652
+ }
653
+
654
+ get canGoBackward(): boolean {
655
+ const firstResource = this.pub.readingOrder.items[0]?.href;
656
+ return this.viewport.progressions.has(firstResource) && this.viewport.progressions.get(firstResource)?.start === 0;
657
+ }
658
+
659
+ get canGoForward(): boolean {
660
+ const lastResource = this.pub.readingOrder.items[this.pub.readingOrder.items.length - 1]?.href;
661
+ return this.viewport.progressions.has(lastResource) && this.viewport.progressions.get(lastResource)?.end === 1;
436
662
  }
437
663
 
438
664
  // TODO: This is temporary until user settings are implemented.
@@ -442,7 +668,7 @@ export class EpubNavigator extends VisualNavigator {
442
668
 
443
669
  // TODO: This is temporary until user settings are implemented.
444
670
  public async setReadingProgression(newProgression: ReadingProgression) {
445
- if(this.currentProgression === newProgression) return;
671
+ if(this.currentProgression === newProgression || !this.framePool) return;
446
672
  this.currentProgression = newProgression;
447
673
  await this.framePool.update(this.pub, this.currentLocator, this.determineModules(), true);
448
674
  this.attachListener();