@internetarchive/bookreader 5.0.0-57 → 5.0.0-59

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. package/BookReader/BookReader.css +110 -39
  2. package/BookReader/BookReader.js +1 -1
  3. package/BookReader/BookReader.js.LICENSE.txt +0 -20
  4. package/BookReader/BookReader.js.map +1 -1
  5. package/BookReader/ia-bookreader-bundle.js +1 -1
  6. package/BookReader/ia-bookreader-bundle.js.map +1 -1
  7. package/BookReader/plugins/plugin.archive_analytics.js +1 -1
  8. package/BookReader/plugins/plugin.archive_analytics.js.map +1 -1
  9. package/BookReader/plugins/plugin.autoplay.js +1 -1
  10. package/BookReader/plugins/plugin.autoplay.js.map +1 -1
  11. package/BookReader/plugins/plugin.resume.js +1 -1
  12. package/BookReader/plugins/plugin.resume.js.map +1 -1
  13. package/BookReader/plugins/plugin.tts.js +1 -1
  14. package/BookReader/plugins/plugin.tts.js.map +1 -1
  15. package/BookReader/plugins/plugin.url.js +1 -1
  16. package/BookReader/plugins/plugin.url.js.map +1 -1
  17. package/BookReaderDemo/BookReaderJSAutoplay.js +4 -1
  18. package/BookReaderDemo/BookReaderJSSimple.js +1 -0
  19. package/BookReaderDemo/IADemoBr.js +1 -2
  20. package/CHANGELOG.md +8 -0
  21. package/babel.config.js +5 -2
  22. package/package.json +10 -9
  23. package/src/BookReader/BookModel.js +59 -1
  24. package/src/BookReader/Mode1Up.js +5 -0
  25. package/src/BookReader/Mode1UpLit.js +19 -73
  26. package/src/BookReader/Mode2Up.js +72 -1332
  27. package/src/BookReader/Mode2UpLit.js +774 -0
  28. package/src/BookReader/ModeCoordinateSpace.js +29 -0
  29. package/src/BookReader/ModeSmoothZoom.js +32 -0
  30. package/src/BookReader/options.js +8 -2
  31. package/src/BookReader/utils.js +16 -0
  32. package/src/BookReader.js +24 -217
  33. package/src/css/_BRBookmarks.scss +1 -1
  34. package/src/css/_BRmain.scss +14 -0
  35. package/src/css/_BRpages.scss +113 -41
  36. package/src/plugins/plugin.autoplay.js +1 -6
  37. package/src/plugins/tts/WebTTSEngine.js +2 -2
  38. package/src/plugins/tts/plugin.tts.js +3 -17
  39. package/src/plugins/tts/utils.js +0 -16
  40. package/tests/e2e/helpers/base.js +20 -20
  41. package/tests/e2e/helpers/rightToLeft.js +4 -10
  42. package/tests/e2e/viewmode.test.js +10 -8
  43. package/tests/jest/BookReader/BookModel.test.js +25 -0
  44. package/tests/jest/BookReader/BookReaderPublicFunctions.test.js +28 -11
  45. package/tests/jest/BookReader/Mode1UpLit.test.js +0 -19
  46. package/tests/jest/BookReader/Mode2Up.test.js +55 -225
  47. package/tests/jest/BookReader/Mode2UpLit.test.js +190 -0
  48. package/tests/jest/BookReader/ModeCoordinateSpace.test.js +16 -0
  49. package/tests/jest/BookReader/ModeSmoothZoom.test.js +26 -0
  50. package/tests/jest/BookReader/Navbar/Navbar.test.js +3 -3
  51. package/tests/jest/BookReader/utils.test.js +32 -1
  52. package/tests/jest/plugins/tts/utils.test.js +0 -34
  53. package/tests/jest/setup.js +3 -0
@@ -0,0 +1,29 @@
1
+ import { calcScreenDPI } from './utils';
2
+
3
+ /**
4
+ * There are a few different "coordinate spaces" at play in BR:
5
+ * (1) World units: i.e. inches. Unless otherwise stated, all computations
6
+ * are done in world units.
7
+ * (2) Rendered Pixels: i.e. img.width = '300'. Note this does _not_ take
8
+ * into account zoom scaling.
9
+ * (3) Visible Pixels: Just rendered pixels, but taking into account scaling.
10
+ */
11
+ export class ModeCoordinateSpace {
12
+ screenDPI = calcScreenDPI();
13
+
14
+ /**
15
+ * @param {{ scale: number }} mode
16
+ */
17
+ constructor(mode) {
18
+ this.mode = mode;
19
+ }
20
+
21
+ worldUnitsToRenderedPixels = (/** @type {number} */inches) => inches * this.screenDPI;
22
+ renderedPixelsToWorldUnits = (/** @type {number} */px) => px / this.screenDPI;
23
+
24
+ renderedPixelsToVisiblePixels = (/** @type {number} */px) => px * this.mode.scale;
25
+ visiblePixelsToRenderedPixels = (/** @type {number} */px) => px / this.mode.scale;
26
+
27
+ worldUnitsToVisiblePixels = (/** @type {number} */px) => this.renderedPixelsToVisiblePixels(this.worldUnitsToRenderedPixels(px));
28
+ visiblePixelsToWorldUnits = (/** @type {number} */px) => this.renderedPixelsToWorldUnits(this.visiblePixelsToRenderedPixels(px));
29
+ }
@@ -6,6 +6,7 @@ import Hammer from "hammerjs";
6
6
  * @typedef {object} SmoothZoomable
7
7
  * @property {HTMLElement} $container
8
8
  * @property {HTMLElement} $visibleWorld
9
+ * @property {import("./options.js").AutoFitValues} autoFit
9
10
  * @property {number} scale
10
11
  * @property {{ x: number, y: number }} scaleCenter
11
12
  * @property {HTMLDimensionsCacher} htmlDimensionsCacher
@@ -91,6 +92,7 @@ export class ModeSmoothZoom {
91
92
  this.oldScale = 1;
92
93
  this.mode.$visibleWorld.classList.add("BRsmooth-zooming");
93
94
  this.mode.$visibleWorld.style.willChange = "transform";
95
+ this.mode.autoFit = "none";
94
96
  this.detachCtrlZoom();
95
97
  this.mode.detachScrollListeners?.();
96
98
  }
@@ -161,6 +163,7 @@ export class ModeSmoothZoom {
161
163
 
162
164
  // Zoom around the cursor
163
165
  this.updateScaleCenter(ev);
166
+ this.mode.autoFit = "none";
164
167
  this.mode.scale *= 1 - Math.sign(ev.deltaY) * zoomMultiplier;
165
168
  }
166
169
 
@@ -176,4 +179,33 @@ export class ModeSmoothZoom {
176
179
  y: (clientY - bc.top) / this.mode.htmlDimensionsCacher.clientHeight,
177
180
  };
178
181
  }
182
+
183
+ /**
184
+ * @param {number} newScale
185
+ * @param {number} oldScale
186
+ */
187
+ updateViewportOnZoom(newScale, oldScale) {
188
+ const container = this.mode.$container;
189
+ const { scrollTop: T, scrollLeft: L } = container;
190
+ const W = this.mode.htmlDimensionsCacher.clientWidth;
191
+ const H = this.mode.htmlDimensionsCacher.clientHeight;
192
+
193
+ // Scale factor change
194
+ const F = newScale / oldScale;
195
+
196
+ // Where in the viewport the zoom is centered on
197
+ const XPOS = this.mode.scaleCenter.x;
198
+ const YPOS = this.mode.scaleCenter.y;
199
+ const oldCenter = {
200
+ x: L + XPOS * W,
201
+ y: T + YPOS * H,
202
+ };
203
+ const newCenter = {
204
+ x: F * oldCenter.x,
205
+ y: F * oldCenter.y,
206
+ };
207
+
208
+ container.scrollTop = newCenter.y - YPOS * H;
209
+ container.scrollLeft = newCenter.x - XPOS * W;
210
+ }
179
211
  }
@@ -31,7 +31,7 @@ export const DEFAULT_OPTIONS = {
31
31
  thumbMaxZoomColumns: 8,
32
32
 
33
33
  /** @type {number | 'fast' | 'slow'} speed for flip animation */
34
- flipSpeed: 'fast',
34
+ flipSpeed: 400,
35
35
 
36
36
  showToolbar: true,
37
37
  showNavbar: true,
@@ -300,7 +300,13 @@ export const DEFAULT_OPTIONS = {
300
300
  useSrcSet: false,
301
301
  };
302
302
 
303
- /** @typedef {'width' | 'height' | 'auto' | 'none'} AutoFitValues */
303
+ /**
304
+ * @typedef {'width' | 'height' | 'auto' | 'none'} AutoFitValues
305
+ * - width: fill the width of the container
306
+ * - height: fill the height of the container
307
+ * - auto: fill the width or height of the container, whichever is smaller
308
+ * - none: do not autofit
309
+ **/
304
310
 
305
311
  /**
306
312
  * @typedef {object} ReductionFactor
@@ -262,3 +262,19 @@ export async function poll(fn, { step = 50, timeout = 500, until = val => Boolea
262
262
  await _sleep(step);
263
263
  }
264
264
  }
265
+
266
+ /**
267
+ * Convert a EventTarget style event into a promise
268
+ * @param {EventTarget} target
269
+ * @param {string} eventType
270
+ * @return {Promise<Event>}
271
+ */
272
+ export function promisifyEvent(target, eventType) {
273
+ return new Promise(res => {
274
+ const resolver = ev => {
275
+ target.removeEventListener(eventType, resolver);
276
+ res(ev);
277
+ };
278
+ target.addEventListener(eventType, resolver);
279
+ });
280
+ }
package/src/BookReader.js CHANGED
@@ -19,9 +19,6 @@ This file is part of BookReader.
19
19
  The BookReader source is hosted at http://github.com/internetarchive/bookreader/
20
20
 
21
21
  */
22
- // effect.js gives acces to extra easing function (e.g. easeInOutExpo)
23
- import 'jquery-ui/ui/effect.js';
24
-
25
22
  // Needed by touch-punch
26
23
  import 'jquery-ui/ui/widget.js';
27
24
  import 'jquery-ui/ui/widgets/mouse.js';
@@ -155,10 +152,11 @@ BookReader.prototype.setup = function(options) {
155
152
  this.displayedIndices = [];
156
153
 
157
154
  this.animating = false;
158
- this.flipSpeed = options.flipSpeed;
155
+ this.flipSpeed = typeof options.flipSpeed === 'number' ? options.flipSpeed : {
156
+ 'fast': 200,
157
+ 'slow': 600,
158
+ }[options.flipSpeed] || 400;
159
159
  this.flipDelay = options.flipDelay;
160
- this.twoPagePopUp = null;
161
- this.leafEdgeTmp = null;
162
160
 
163
161
  /**
164
162
  * Represents the first displayed index
@@ -167,7 +165,6 @@ BookReader.prototype.setup = function(options) {
167
165
  * @property {number|null} firstIndex
168
166
  */
169
167
  this.firstIndex = null;
170
- this.lastDisplayableIndex2up = null;
171
168
  this.isFullscreenActive = options.startFullscreen || false;
172
169
  this.lastScroll = null;
173
170
 
@@ -266,7 +263,7 @@ BookReader.prototype.setup = function(options) {
266
263
  * Includes cached elements which might be rendered again.
267
264
  */
268
265
  BookReader.prototype.getActivePageContainerElements = function() {
269
- let containerEls = Object.values(this._modes.mode2Up.pageContainers).map(pc => pc.$container[0])
266
+ let containerEls = Object.values(this._modes.mode2Up.mode2UpLit.pageContainerCache).map(pc => pc.$container[0])
270
267
  .concat(Object.values(this._modes.mode1Up.mode1UpLit.pageContainerCache).map(pc => pc.$container[0]));
271
268
  if (this.mode == this.constModeThumb) {
272
269
  containerEls = containerEls.concat(this.$('.BRpagecontainer').toArray());
@@ -281,7 +278,7 @@ BookReader.prototype.getActivePageContainerElements = function() {
281
278
  */
282
279
  BookReader.prototype.getActivePageContainerElementsForIndex = function(pageIndex) {
283
280
  return [
284
- this._modes.mode2Up.pageContainers[pageIndex]?.$container?.[0],
281
+ this._modes.mode2Up.mode2UpLit.pageContainerCache[pageIndex]?.$container?.[0],
285
282
  this._modes.mode1Up.mode1UpLit.pageContainerCache[pageIndex]?.$container?.[0],
286
283
  ...(this.mode == this.constModeThumb ? this.$(`.pagediv${pageIndex}`).toArray() : [])
287
284
  ].filter(x => x);
@@ -648,27 +645,7 @@ BookReader.prototype.resize = function() {
648
645
  } else if (this.constModeThumb == this.mode) {
649
646
  this._modes.modeThumb.prepare();
650
647
  } else {
651
- // We only need to prepare again in autofit (size of spread changes)
652
- if (this.twoPage.autofit) {
653
- // most common path, esp. for archive.org books
654
- this._modes.mode2Up.prepare();
655
- } else {
656
- // used when zoomed in
657
- // Re-center if the scrollbars have disappeared
658
- const center = this.twoPageGetViewCenter();
659
- let doRecenter = false;
660
- if (this.twoPage.totalWidth < this.refs.$brContainer.prop('clientWidth')) {
661
- center.percentageX = 0.5;
662
- doRecenter = true;
663
- }
664
- if (this.twoPage.totalHeight < this.refs.$brContainer.prop('clientHeight')) {
665
- center.percentageY = 0.5;
666
- doRecenter = true;
667
- }
668
- if (doRecenter) {
669
- this._modes.mode2Up.centerView(center.percentageX, center.percentageY);
670
- }
671
- }
648
+ this._modes.mode2Up.resizePageView();
672
649
  }
673
650
  this.trigger(BookReader.eventNames.resize);
674
651
  };
@@ -1012,13 +989,7 @@ BookReader.prototype.jumpToIndex = function(index, pageX, pageY, noAnimate) {
1012
989
 
1013
990
  this.trigger(BookReader.eventNames.stop);
1014
991
 
1015
- if (this.constMode2up == this.mode) {
1016
- this._modes.mode2Up.jumpToIndex(index);
1017
- } else if (this.constModeThumb == this.mode) {
1018
- this._modes.modeThumb.jumpToIndex(index);
1019
- } else { // 1up
1020
- this._modes.mode1Up.jumpToIndex(index, pageX, pageY, noAnimate);
1021
- }
992
+ this.activeMode.jumpToIndex(index, pageX, pageY, noAnimate);
1022
993
  };
1023
994
 
1024
995
  /**
@@ -1084,16 +1055,7 @@ BookReader.prototype.switchMode = function(
1084
1055
  this.reduce = this.quantizeReduce(this.reduce, this.reductionFactors);
1085
1056
  this._modes.modeThumb.prepare();
1086
1057
  } else {
1087
- // $$$ why don't we save autofit?
1088
- // this.twoPage.autofit = null; // Take zoom level from other mode
1089
- // spread indices not set, so let's set them
1090
- if (init || !pageFound) {
1091
- this._modes.mode2Up.setSpreadIndices();
1092
- }
1093
-
1094
- this._modes.mode2Up.calculateReductionFactors(); // this sets this.twoPage && this.reduce
1095
1058
  this._modes.mode2Up.prepare();
1096
- this._modes.mode2Up.centerView(0.5, 0.5); // $$$ TODO preserve center
1097
1059
  }
1098
1060
 
1099
1061
  if (!(this.suppressFragmentChange || suppressFragmentChange)) {
@@ -1108,11 +1070,11 @@ BookReader.prototype.switchMode = function(
1108
1070
  BookReader.prototype.updateBrClasses = function() {
1109
1071
  const modeToClass = {};
1110
1072
  modeToClass[this.constMode1up] = 'BRmode1up';
1111
- modeToClass[this.constMode2up] = 'BRmode2Up';
1073
+ modeToClass[this.constMode2up] = 'BRmode2up';
1112
1074
  modeToClass[this.constModeThumb] = 'BRmodeThumb';
1113
1075
 
1114
1076
  this.refs.$br
1115
- .removeClass('BRmode1up BRmode2Up BRmodeThumb')
1077
+ .removeClass('BRmode1up BRmode2up BRmodeThumb')
1116
1078
  .addClass(modeToClass[this.mode]);
1117
1079
 
1118
1080
  if (this.isFullscreen()) {
@@ -1143,14 +1105,13 @@ BookReader.prototype.toggleFullscreen = async function(bindKeyboardControls = tr
1143
1105
  /**
1144
1106
  * Enters fullscreen
1145
1107
  * including:
1146
- * - animation
1147
1108
  * - binds keyboard controls
1148
1109
  * - fires custom event
1149
1110
  * @param { boolean } bindKeyboardControls
1150
1111
  */
1151
1112
  BookReader.prototype.enterFullscreen = async function(bindKeyboardControls = true) {
1113
+ this.refs.$br.addClass('BRfullscreenAnimation');
1152
1114
  const currentIndex = this.currentIndex();
1153
- this.refs.$brContainer.css('opacity', 0);
1154
1115
 
1155
1116
  if (bindKeyboardControls) {
1156
1117
  this._fullscreenCloseHandler = (e) => {
@@ -1167,8 +1128,6 @@ BookReader.prototype.enterFullscreen = async function(bindKeyboardControls = tru
1167
1128
  this.isFullscreenActive = true;
1168
1129
  // prioritize class updates so CSS can propagate
1169
1130
  this.updateBrClasses();
1170
- this.animating = true;
1171
- await new Promise(res => this.refs.$brContainer.animate({opacity: 1}, 'fast', 'linear', res));
1172
1131
  if (this.activeMode instanceof Mode1Up) {
1173
1132
  this.activeMode.mode1UpLit.scale = this.activeMode.mode1UpLit.computeDefaultScale(this.book.getPage(currentIndex));
1174
1133
  // Need the new scale to be applied before calling jumpToIndex
@@ -1176,7 +1135,6 @@ BookReader.prototype.enterFullscreen = async function(bindKeyboardControls = tru
1176
1135
  await this.activeMode.mode1UpLit.updateComplete;
1177
1136
  }
1178
1137
  this.jumpToIndex(currentIndex);
1179
- this.animating = false;
1180
1138
 
1181
1139
  this.textSelectionPlugin?.stopPageFlip(this.refs.$brContainer);
1182
1140
  // Add "?view=theater"
@@ -1185,10 +1143,11 @@ BookReader.prototype.enterFullscreen = async function(bindKeyboardControls = tru
1185
1143
  // class updates happen before book-nav relays to web components
1186
1144
  this.trigger(BookReader.eventNames.fullscreenToggled);
1187
1145
 
1188
- setTimeout(() => {
1189
- // resize book after all events & css updates
1190
- this.resize();
1191
- }, 0);
1146
+ // resize book after all events & css updates
1147
+ await new Promise(resolve => setTimeout(resolve, 0));
1148
+
1149
+ this.resize();
1150
+ this.refs.$br.removeClass('BRfullscreenAnimation');
1192
1151
  };
1193
1152
 
1194
1153
  /**
@@ -1199,12 +1158,10 @@ BookReader.prototype.enterFullscreen = async function(bindKeyboardControls = tru
1199
1158
  * @param { boolean } bindKeyboardControls
1200
1159
  */
1201
1160
  BookReader.prototype.exitFullScreen = async function () {
1202
- this.refs.$brContainer.css('opacity', 0);
1203
-
1161
+ this.refs.$br.addClass('BRfullscreenAnimation');
1204
1162
  $(document).off('keyup', this._fullscreenCloseHandler);
1205
1163
 
1206
1164
  const windowWidth = $(window).width();
1207
-
1208
1165
  const canShow2up = this.options.controls.twoPage.visible;
1209
1166
  if (canShow2up && (windowWidth <= this.onePageMinBreakpoint)) {
1210
1167
  this.switchMode(this.constMode2up);
@@ -1216,8 +1173,7 @@ BookReader.prototype.exitFullScreen = async function () {
1216
1173
  this.trigger(BookReader.eventNames.fullscreenToggled);
1217
1174
 
1218
1175
  this.updateBrClasses();
1219
- this.animating = true;
1220
- await new Promise((res => this.refs.$brContainer.animate({opacity: 1}, 'fast', 'linear', res)));
1176
+ await new Promise(resolve => setTimeout(resolve, 0));
1221
1177
  this.resize();
1222
1178
 
1223
1179
  if (this.activeMode instanceof Mode1Up) {
@@ -1226,11 +1182,10 @@ BookReader.prototype.exitFullScreen = async function () {
1226
1182
  await this.activeMode.mode1UpLit.updateComplete;
1227
1183
  }
1228
1184
 
1229
- this.animating = false;
1230
-
1231
1185
  this.textSelectionPlugin?.stopPageFlip(this.refs.$brContainer);
1232
1186
  // Remove "?view=theater"
1233
1187
  this.trigger(BookReader.eventNames.fragmentChange);
1188
+ this.refs.$br.removeClass('BRfullscreenAnimation');
1234
1189
  };
1235
1190
 
1236
1191
  /**
@@ -1329,9 +1284,9 @@ BookReader.prototype.leftmost = function() {
1329
1284
  BookReader.prototype.next = function({triggerStop = true} = {}) {
1330
1285
  if (this.constMode2up == this.mode) {
1331
1286
  if (triggerStop) this.trigger(BookReader.eventNames.stop);
1332
- this._modes.mode2Up.flipFwdToIndex(null);
1287
+ this._modes.mode2Up.mode2UpLit.flipAnimation('next');
1333
1288
  } else {
1334
- if (this.firstIndex < this.lastDisplayableIndex()) {
1289
+ if (this.firstIndex < this.book.getNumLeafs() - 1) {
1335
1290
  this.jumpToIndex(this.firstIndex + 1);
1336
1291
  }
1337
1292
  }
@@ -1343,7 +1298,7 @@ BookReader.prototype.prev = function({triggerStop = true} = {}) {
1343
1298
 
1344
1299
  if (this.constMode2up == this.mode) {
1345
1300
  if (triggerStop) this.trigger(BookReader.eventNames.stop);
1346
- this._modes.mode2Up.flipBackToIndex(null);
1301
+ this._modes.mode2Up.mode2UpLit.flipAnimation('prev');
1347
1302
  } else {
1348
1303
  if (this.firstIndex >= 1) {
1349
1304
  this.jumpToIndex(this.firstIndex - 1);
@@ -1352,87 +1307,13 @@ BookReader.prototype.prev = function({triggerStop = true} = {}) {
1352
1307
  };
1353
1308
 
1354
1309
  BookReader.prototype.first = function() {
1355
- this.jumpToIndex(this.firstDisplayableIndex());
1310
+ this.jumpToIndex(0);
1356
1311
  };
1357
1312
 
1358
1313
  BookReader.prototype.last = function() {
1359
- this.jumpToIndex(this.lastDisplayableIndex());
1360
- };
1361
-
1362
- /**
1363
- * Scrolls down one screen view
1364
- */
1365
- BookReader.prototype.scrollDown = function() {
1366
- if ($.inArray(this.mode, [this.constMode1up, this.constModeThumb]) >= 0) {
1367
- if ( this.mode == this.constMode1up && (this.reduce >= this.onePageGetAutofitHeight()) ) {
1368
- // Whole pages are visible, scroll whole page only
1369
- return this.next();
1370
- }
1371
-
1372
- this.refs.$brContainer.stop(true).animate(
1373
- { scrollTop: '+=' + this._scrollAmount() + 'px'},
1374
- 400, 'easeInOutExpo'
1375
- );
1376
- return true;
1377
- } else {
1378
- return false;
1379
- }
1380
- };
1381
-
1382
- /**
1383
- * Scrolls up one screen view
1384
- */
1385
- BookReader.prototype.scrollUp = function() {
1386
- if ($.inArray(this.mode, [this.constMode1up, this.constModeThumb]) >= 0) {
1387
- if ( this.mode == this.constMode1up && (this.reduce >= this.onePageGetAutofitHeight()) ) {
1388
- // Whole pages are visible, scroll whole page only
1389
- return this.prev();
1390
- }
1391
-
1392
- this.refs.$brContainer.stop(true).animate(
1393
- { scrollTop: '-=' + this._scrollAmount() + 'px'},
1394
- 400, 'easeInOutExpo'
1395
- );
1396
- return true;
1397
- } else {
1398
- return false;
1399
- }
1400
- };
1401
-
1402
- /**
1403
- * The amount to scroll vertically in integer pixels
1404
- */
1405
- BookReader.prototype._scrollAmount = function() {
1406
- if (this.constMode1up == this.mode) {
1407
- // Overlap by % of page size
1408
- return parseInt(this.refs.$brContainer.prop('clientHeight') - this.book.getPageHeight(this.currentIndex()) / this.reduce * 0.03);
1409
- }
1410
-
1411
- return parseInt(0.9 * this.refs.$brContainer.prop('clientHeight'));
1314
+ this.jumpToIndex(this.book.getNumLeafs() - 1);
1412
1315
  };
1413
1316
 
1414
- /**
1415
- * Immediately stop flip animations. Callbacks are triggered.
1416
- */
1417
- BookReader.prototype.stopFlipAnimations = function() {
1418
- this.trigger(BookReader.eventNames.stop);
1419
-
1420
- // Stop animation, clear queue, trigger callbacks
1421
- if (this.leafEdgeTmp) {
1422
- $(this.leafEdgeTmp).stop(false, true);
1423
- }
1424
- jQuery.each(this._modes.mode2Up.pageContainers, function() {
1425
- $(this.$container).stop(false, true);
1426
- });
1427
-
1428
- // And again since animations also queued in callbacks
1429
- if (this.leafEdgeTmp) {
1430
- $(this.leafEdgeTmp).stop(false, true);
1431
- }
1432
- jQuery.each(this._modes.mode2Up.pageContainers, function() {
1433
- $(this.$container).stop(false, true);
1434
- });
1435
- };
1436
1317
 
1437
1318
  /**
1438
1319
  * @template TClass extends { br: BookReader }
@@ -1492,20 +1373,6 @@ BookReader.prototype.bindNavigationHandlers = function() {
1492
1373
  this.trigger(BookReader.eventNames.stop);
1493
1374
  this.right();
1494
1375
  },
1495
- book_up: () => {
1496
- if ($.inArray(this.mode, [this.constMode1up, this.constModeThumb]) >= 0) {
1497
- this.scrollUp();
1498
- } else {
1499
- this.prev();
1500
- }
1501
- },
1502
- book_down: () => {
1503
- if ($.inArray(this.mode, [this.constMode1up, this.constModeThumb]) >= 0) {
1504
- this.scrollDown();
1505
- } else {
1506
- this.next();
1507
- }
1508
- },
1509
1376
  book_top: this.first.bind(this),
1510
1377
  book_bottom: this.last.bind(this),
1511
1378
  book_leftmost: this.leftmost.bind(this),
@@ -1881,64 +1748,6 @@ BookReader.prototype.showNavigation = function() {
1881
1748
  }
1882
1749
  };
1883
1750
 
1884
- /**
1885
- * Returns the index of the first visible page, dependent on the mode.
1886
- * $$$ Currently we cannot display the front/back cover in 2-up and will need to update
1887
- * this function when we can as part of https://bugs.launchpad.net/gnubook/+bug/296788
1888
- * @return {number}
1889
- */
1890
- BookReader.prototype.firstDisplayableIndex = function() {
1891
- if (this.mode != this.constMode2up) {
1892
- return 0;
1893
- }
1894
-
1895
- if ('rl' != this.pageProgression) {
1896
- // LTR
1897
- if (this.book.getPageSide(0) == 'L') {
1898
- return 0;
1899
- } else {
1900
- return -1;
1901
- }
1902
- } else {
1903
- // RTL
1904
- if (this.book.getPageSide(0) == 'R') {
1905
- return 0;
1906
- } else {
1907
- return -1;
1908
- }
1909
- }
1910
- };
1911
-
1912
- /**
1913
- * Returns the index of the last visible page, dependent on the mode.
1914
- * $$$ Currently we cannot display the front/back cover in 2-up and will need to update
1915
- * this function when we can as part of https://bugs.launchpad.net/gnubook/+bug/296788
1916
- * @return {number}
1917
- */
1918
- BookReader.prototype.lastDisplayableIndex = function() {
1919
-
1920
- const lastIndex = this.book.getNumLeafs() - 1;
1921
-
1922
- if (this.mode != this.constMode2up) {
1923
- return lastIndex;
1924
- }
1925
-
1926
- if ('rl' != this.pageProgression) {
1927
- // LTR
1928
- if (this.book.getPageSide(lastIndex) == 'R') {
1929
- return lastIndex;
1930
- } else {
1931
- return lastIndex + 1;
1932
- }
1933
- } else {
1934
- // RTL
1935
- if (this.book.getPageSide(lastIndex) == 'L') {
1936
- return lastIndex;
1937
- } else {
1938
- return lastIndex + 1;
1939
- }
1940
- }
1941
- };
1942
1751
 
1943
1752
 
1944
1753
  /**************************/
@@ -2112,8 +1921,6 @@ BookReader.prototype.initUIStrings = function() {
2112
1921
  '.full': 'Toggle fullscreen',
2113
1922
  '.book_left': 'Flip left',
2114
1923
  '.book_right': 'Flip right',
2115
- '.book_up': 'Page up',
2116
- '.book_down': 'Page down',
2117
1924
  '.play': 'Play',
2118
1925
  '.pause': 'Pause',
2119
1926
  '.BRdn': 'Show/hide nav bar', // Would have to keep updating on state change to have just "Hide nav bar"
@@ -6,7 +6,7 @@
6
6
  z-index: 2;
7
7
  opacity: 0;
8
8
  }
9
- .BRtwopageview .BRpagecontainer[data-side="L"] .bookmark-button {
9
+ .BRmode2up .BRpagecontainer[data-side="L"] .bookmark-button {
10
10
  right: auto;
11
11
  left: 0;
12
12
  }
@@ -249,3 +249,17 @@ body.BRfullscreenActive {
249
249
  .BRpagediv1up { background-color: white; }
250
250
  .BRpagedivthumb { background-color: white; }
251
251
  }
252
+
253
+ .BRfullscreenAnimation .br-mode-2up__book {
254
+ transition: transform .2s ease-in-out;
255
+ }
256
+
257
+ .fullscreenActive.BRmodeThumb .BRcontainer,
258
+ .fullscreenActive.BRmode1up .BRcontainer {
259
+ animation: flash 0.3s ease-in-out;
260
+ }
261
+
262
+ @keyframes flash {
263
+ 0% { opacity: 0; }
264
+ 100% { opacity: 1; }
265
+ }