@internetarchive/bookreader 5.0.0-4 → 5.0.0-40-a1

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 (228) hide show
  1. package/.eslintrc.js +17 -15
  2. package/.github/workflows/node.js.yml +75 -4
  3. package/.github/workflows/npm-publish.yml +2 -16
  4. package/.testcaferc.js +10 -0
  5. package/BookReader/BookReader.css +83 -323
  6. package/BookReader/BookReader.js +1 -1
  7. package/BookReader/BookReader.js.LICENSE.txt +24 -0
  8. package/BookReader/BookReader.js.map +1 -1
  9. package/BookReader/ia-bookreader-bundle.js +1623 -0
  10. package/BookReader/{bookreader-component-bundle.js.LICENSE.txt → ia-bookreader-bundle.js.LICENSE.txt} +14 -10
  11. package/BookReader/ia-bookreader-bundle.js.map +1 -0
  12. package/BookReader/icons/close-circle-dark.svg +1 -0
  13. package/BookReader/icons/magnify-minus.svg +1 -1
  14. package/BookReader/icons/magnify-plus.svg +1 -1
  15. package/BookReader/icons/voice.svg +1 -0
  16. package/BookReader/plugins/plugin.archive_analytics.js +1 -1
  17. package/BookReader/plugins/plugin.archive_analytics.js.map +1 -1
  18. package/BookReader/plugins/plugin.autoplay.js +1 -1
  19. package/BookReader/plugins/plugin.autoplay.js.map +1 -1
  20. package/BookReader/plugins/plugin.chapters.js +1 -1
  21. package/BookReader/plugins/plugin.chapters.js.map +1 -1
  22. package/BookReader/plugins/plugin.iframe.js +1 -1
  23. package/BookReader/plugins/plugin.iframe.js.map +1 -1
  24. package/BookReader/plugins/plugin.mobile_nav.js +1 -1
  25. package/BookReader/plugins/plugin.mobile_nav.js.map +1 -1
  26. package/BookReader/plugins/plugin.resume.js +1 -1
  27. package/BookReader/plugins/plugin.resume.js.map +1 -1
  28. package/BookReader/plugins/plugin.search.js +1 -1
  29. package/BookReader/plugins/plugin.search.js.map +1 -1
  30. package/BookReader/plugins/plugin.text_selection.js +1 -1
  31. package/BookReader/plugins/plugin.text_selection.js.map +1 -1
  32. package/BookReader/plugins/plugin.tts.js +1 -1
  33. package/BookReader/plugins/plugin.tts.js.map +1 -1
  34. package/BookReader/plugins/plugin.url.js +1 -1
  35. package/BookReader/plugins/plugin.url.js.map +1 -1
  36. package/BookReader/plugins/plugin.vendor-fullscreen.js +1 -1
  37. package/BookReader/plugins/plugin.vendor-fullscreen.js.map +1 -1
  38. package/BookReader/webcomponents-bundle.js +3 -0
  39. package/BookReader/webcomponents-bundle.js.LICENSE.txt +9 -0
  40. package/BookReader/webcomponents-bundle.js.map +1 -0
  41. package/BookReaderDemo/BookReaderDemo.css +14 -1
  42. package/BookReaderDemo/IADemoBr.js +120 -0
  43. package/BookReaderDemo/demo-advanced.html +1 -1
  44. package/BookReaderDemo/demo-autoplay.html +1 -0
  45. package/BookReaderDemo/demo-embed-iframe-src.html +1 -0
  46. package/BookReaderDemo/demo-fullscreen-mobile.html +1 -0
  47. package/BookReaderDemo/demo-fullscreen.html +1 -0
  48. package/BookReaderDemo/demo-iiif.html +1 -0
  49. package/BookReaderDemo/demo-internetarchive.html +74 -17
  50. package/BookReaderDemo/demo-multiple.html +1 -0
  51. package/BookReaderDemo/demo-preview-pages.html +1 -0
  52. package/BookReaderDemo/demo-simple.html +1 -0
  53. package/BookReaderDemo/demo-vendor-fullscreen.html +1 -0
  54. package/BookReaderDemo/ia-multiple-volumes-manifest.js +170 -0
  55. package/BookReaderDemo/immersion-1up.html +1 -0
  56. package/BookReaderDemo/immersion-mode.html +1 -0
  57. package/BookReaderDemo/toggle_controls.html +1 -0
  58. package/BookReaderDemo/view_mode.html +1 -0
  59. package/BookReaderDemo/viewmode-cycle.html +1 -2
  60. package/CHANGELOG.md +166 -0
  61. package/README.md +14 -1
  62. package/babel.config.js +18 -0
  63. package/codecov.yml +6 -0
  64. package/index.html +3 -0
  65. package/jsconfig.json +19 -0
  66. package/package.json +62 -47
  67. package/renovate.json +43 -0
  68. package/src/BookNavigator/assets/bookmark-colors.js +1 -1
  69. package/src/BookNavigator/assets/button-base.js +9 -2
  70. package/src/BookNavigator/assets/ia-logo.js +17 -0
  71. package/src/BookNavigator/assets/icon_checkmark.js +1 -1
  72. package/src/BookNavigator/assets/icon_close.js +1 -1
  73. package/src/BookNavigator/assets/icon_sort_asc.js +5 -0
  74. package/src/BookNavigator/assets/icon_sort_desc.js +5 -0
  75. package/src/BookNavigator/assets/icon_sort_neutral.js +5 -0
  76. package/src/BookNavigator/assets/icon_volumes.js +11 -0
  77. package/src/BookNavigator/book-navigator.js +556 -0
  78. package/src/BookNavigator/bookmarks/bookmark-button.js +3 -2
  79. package/src/BookNavigator/bookmarks/bookmark-edit.js +4 -4
  80. package/src/BookNavigator/bookmarks/bookmarks-list.js +3 -3
  81. package/src/BookNavigator/bookmarks/bookmarks-loginCTA.js +3 -8
  82. package/src/BookNavigator/bookmarks/bookmarks-provider.js +23 -12
  83. package/src/BookNavigator/bookmarks/ia-bookmarks.js +98 -62
  84. package/src/BookNavigator/delete-modal-actions.js +1 -1
  85. package/src/BookNavigator/downloads/downloads-provider.js +23 -17
  86. package/src/BookNavigator/downloads/downloads.js +17 -25
  87. package/src/BookNavigator/search/a-search-result.js +3 -3
  88. package/src/BookNavigator/search/search-provider.js +57 -24
  89. package/src/BookNavigator/search/search-results.js +8 -20
  90. package/src/BookNavigator/sharing.js +27 -0
  91. package/src/BookNavigator/visual-adjustments/visual-adjustments-provider.js +11 -13
  92. package/src/BookNavigator/visual-adjustments/visual-adjustments.js +4 -3
  93. package/src/BookNavigator/volumes/volumes-provider.js +114 -0
  94. package/src/BookNavigator/volumes/volumes.js +188 -0
  95. package/src/BookReader/DebugConsole.js +3 -3
  96. package/src/BookReader/DragScrollable.js +233 -0
  97. package/src/BookReader/Mode1Up.js +51 -351
  98. package/src/BookReader/Mode1UpLit.js +441 -0
  99. package/src/BookReader/Mode2Up.js +104 -71
  100. package/src/BookReader/ModeSmoothZoom.js +179 -0
  101. package/src/BookReader/ModeThumb.js +16 -8
  102. package/src/BookReader/Navbar/Navbar.js +2 -31
  103. package/src/BookReader/PageContainer.js +57 -6
  104. package/src/BookReader/ReduceSet.js +1 -1
  105. package/src/BookReader/Toolbar/Toolbar.js +7 -7
  106. package/src/BookReader/options.js +10 -0
  107. package/src/BookReader/utils/HTMLDimensionsCacher.js +44 -0
  108. package/src/BookReader/utils/ScrollClassAdder.js +31 -0
  109. package/src/BookReader/utils.js +68 -13
  110. package/src/BookReader.js +375 -289
  111. package/src/assets/icons/close-circle-dark.svg +1 -0
  112. package/src/assets/icons/magnify-minus.svg +3 -7
  113. package/src/assets/icons/magnify-plus.svg +3 -7
  114. package/src/assets/icons/voice.svg +1 -0
  115. package/src/css/BookReader.scss +0 -12
  116. package/src/css/_BRComponent.scss +1 -1
  117. package/src/css/_BRmain.scss +19 -24
  118. package/src/css/_BRnav.scss +4 -26
  119. package/src/css/_BRpages.scss +35 -0
  120. package/src/css/_BRsearch.scss +11 -215
  121. package/src/css/_TextSelection.scss +14 -17
  122. package/src/css/_colorbox.scss +2 -2
  123. package/src/css/_controls.scss +16 -3
  124. package/src/css/_icons.scss +6 -0
  125. package/src/ia-bookreader/ia-bookreader.js +224 -0
  126. package/src/plugins/plugin.chapters.js +26 -33
  127. package/src/plugins/plugin.mobile_nav.js +11 -10
  128. package/src/plugins/plugin.resume.js +3 -3
  129. package/src/plugins/plugin.text_selection.js +26 -39
  130. package/src/plugins/plugin.vendor-fullscreen.js +4 -4
  131. package/src/plugins/search/plugin.search.js +106 -107
  132. package/src/plugins/search/view.js +50 -163
  133. package/src/plugins/tts/AbstractTTSEngine.js +46 -37
  134. package/src/plugins/tts/FestivalTTSEngine.js +12 -13
  135. package/src/plugins/tts/PageChunk.js +15 -21
  136. package/src/plugins/tts/PageChunkIterator.js +8 -12
  137. package/src/plugins/tts/WebTTSEngine.js +64 -68
  138. package/src/plugins/tts/plugin.tts.js +79 -108
  139. package/src/plugins/url/UrlPlugin.js +184 -0
  140. package/src/plugins/{plugin.url.js → url/plugin.url.js} +28 -6
  141. package/tests/e2e/README.md +37 -0
  142. package/tests/e2e/autoplay.test.js +2 -2
  143. package/tests/e2e/base.test.js +7 -7
  144. package/tests/e2e/helpers/base.js +8 -3
  145. package/tests/e2e/helpers/debug.js +1 -1
  146. package/tests/e2e/helpers/desktopSearch.js +14 -13
  147. package/tests/e2e/helpers/mobileSearch.js +3 -3
  148. package/tests/e2e/helpers/params.js +17 -0
  149. package/tests/e2e/models/Navigation.js +12 -3
  150. package/tests/e2e/rightToLeft.test.js +4 -5
  151. package/tests/e2e/viewmode.test.js +38 -33
  152. package/tests/{BookReader → jest/BookReader}/BookModel.test.js +3 -3
  153. package/tests/jest/BookReader/BookReaderPublicFunctions.test.js +176 -0
  154. package/tests/{BookReader → jest/BookReader}/DebugConsole.test.js +1 -1
  155. package/tests/{BookReader → jest/BookReader}/ImageCache.test.js +4 -4
  156. package/tests/jest/BookReader/Mode1UpLit.test.js +88 -0
  157. package/tests/{BookReader → jest/BookReader}/Mode2Up.test.js +5 -7
  158. package/tests/jest/BookReader/ModeSmoothZoom.test.js +149 -0
  159. package/tests/jest/BookReader/ModeThumb.test.js +71 -0
  160. package/tests/{BookReader → jest/BookReader}/Navbar/Navbar.test.js +7 -7
  161. package/tests/{BookReader → jest/BookReader}/PageContainer.test.js +79 -6
  162. package/tests/{BookReader → jest/BookReader}/ReduceSet.test.js +1 -1
  163. package/tests/{BookReader → jest/BookReader}/Toolbar/Toolbar.test.js +2 -2
  164. package/tests/jest/BookReader/utils/HTMLDimensionsCacher.test.js +59 -0
  165. package/tests/jest/BookReader/utils/ScrollClassAdder.test.js +49 -0
  166. package/tests/{BookReader → jest/BookReader}/utils/classes.test.js +1 -1
  167. package/tests/jest/BookReader/utils.test.js +136 -0
  168. package/tests/jest/BookReader.keyboard.test.js +190 -0
  169. package/tests/{BookReader.options.test.js → jest/BookReader.options.test.js} +9 -1
  170. package/tests/{BookReader.test.js → jest/BookReader.test.js} +20 -4
  171. package/tests/{plugins → jest/plugins}/plugin.archive_analytics.test.js +2 -2
  172. package/tests/{plugins → jest/plugins}/plugin.autoplay.test.js +2 -2
  173. package/tests/{plugins → jest/plugins}/plugin.chapters.test.js +8 -8
  174. package/tests/{plugins → jest/plugins}/plugin.iframe.test.js +2 -2
  175. package/tests/{plugins → jest/plugins}/plugin.mobile_nav.test.js +5 -5
  176. package/tests/{plugins → jest/plugins}/plugin.resume.test.js +3 -3
  177. package/tests/{plugins → jest/plugins}/plugin.text_selection.test.js +39 -47
  178. package/tests/{plugins → jest/plugins}/plugin.vendor-fullscreen.test.js +2 -2
  179. package/tests/{plugins → jest/plugins}/search/plugin.search.test.js +24 -25
  180. package/tests/{plugins → jest/plugins}/search/plugin.search.view.test.js +6 -6
  181. package/tests/{plugins → jest/plugins}/tts/AbstractTTSEngine.test.js +6 -6
  182. package/tests/{plugins → jest/plugins}/tts/FestivalTTSEngine.test.js +4 -4
  183. package/tests/{plugins → jest/plugins}/tts/PageChunk.test.js +1 -1
  184. package/tests/{plugins → jest/plugins}/tts/PageChunkIterator.test.js +3 -3
  185. package/tests/{plugins → jest/plugins}/tts/WebTTSEngine.test.js +1 -1
  186. package/tests/{plugins → jest/plugins}/tts/utils.test.js +3 -3
  187. package/tests/jest/plugins/url/UrlPlugin.test.js +190 -0
  188. package/tests/{plugins → jest/plugins/url}/plugin.url.test.js +33 -14
  189. package/tests/{util → jest/util}/browserSniffing.test.js +1 -1
  190. package/tests/{util → jest/util}/docCookies.test.js +1 -1
  191. package/tests/{util → jest/util}/strings.test.js +1 -1
  192. package/tests/{utils.js → jest/utils.js} +38 -0
  193. package/tests/karma/BookNavigator/book-navigator.test.js +501 -0
  194. package/tests/karma/BookNavigator/bookmarks/bookmark-button.test.js +44 -0
  195. package/tests/karma/BookNavigator/bookmarks/bookmark-edit.test.js +1 -3
  196. package/tests/karma/BookNavigator/bookmarks/bookmarks-list.test.js +3 -4
  197. package/tests/karma/BookNavigator/bookmarks/ia-bookmarks.test.js +57 -0
  198. package/tests/karma/BookNavigator/downloads/downloads-provider.test.js +67 -0
  199. package/tests/karma/BookNavigator/downloads/downloads.test.js +54 -0
  200. package/tests/karma/BookNavigator/search/search-provider.test.js +123 -0
  201. package/tests/karma/BookNavigator/{search-results.test.js → search/search-results.test.js} +1 -4
  202. package/tests/karma/BookNavigator/sharing/sharing-provider.test.js +49 -0
  203. package/tests/karma/BookNavigator/visual-adjustments.test.js +0 -2
  204. package/tests/karma/BookNavigator/volumes/volumes-provider.test.js +184 -0
  205. package/tests/karma/BookNavigator/volumes/volumes.test.js +98 -0
  206. package/webpack.config.js +10 -4
  207. package/.babelrc +0 -12
  208. package/.dependabot/config.yml +0 -6
  209. package/.testcaferc.json +0 -5
  210. package/BookReader/bookreader-component-bundle.js +0 -1450
  211. package/BookReader/bookreader-component-bundle.js.map +0 -1
  212. package/BookReader/plugins/plugin.menu_toggle.js +0 -2
  213. package/BookReader/plugins/plugin.menu_toggle.js.map +0 -1
  214. package/BookReaderDemo/bookreader-template-bundle.js +0 -7178
  215. package/BookReaderDemo/demo-plugin-menu-toggle.html +0 -34
  216. package/src/BookNavigator/BookModel.js +0 -14
  217. package/src/BookNavigator/BookNavigator.js +0 -435
  218. package/src/BookNavigator/assets/book-loader.js +0 -27
  219. package/src/BookNavigator/br-fullscreen-mgr.js +0 -83
  220. package/src/BookReaderComponent/BookReaderComponent.js +0 -112
  221. package/src/ItemNavigator/ItemNavigator.js +0 -372
  222. package/src/ItemNavigator/providers/sharing.js +0 -29
  223. package/src/dragscrollable-br.js +0 -261
  224. package/src/plugins/menu_toggle/plugin.menu_toggle.js +0 -324
  225. package/tests/BookReader/BookReaderPublicFunctions.test.js +0 -171
  226. package/tests/BookReader/Mode1Up.test.js +0 -164
  227. package/tests/BookReader/utils.test.js +0 -109
  228. package/tests/plugins/menu_toggle/plugin.menu_toggle.test.js +0 -68
@@ -1,16 +1,21 @@
1
1
  // @ts-check
2
2
  // effect.js gives acces to extra easing function (e.g. easeInSine)
3
3
  import 'jquery-ui/ui/effect.js';
4
- import '../dragscrollable-br.js';
5
4
  import { clamp } from './utils.js';
6
5
  import { EVENTS } from './events.js';
6
+ import { ModeSmoothZoom } from "./ModeSmoothZoom.js";
7
+ import { HTMLDimensionsCacher } from './utils/HTMLDimensionsCacher.js';
8
+ import { DragScrollable } from './DragScrollable.js';
9
+ import { ScrollClassAdder } from './utils/ScrollClassAdder.js';
7
10
 
8
11
  /** @typedef {import('../BookReader.js').default} BookReader */
9
12
  /** @typedef {import('./BookModel.js').BookModel} BookModel */
10
13
  /** @typedef {import('./BookModel.js').PageIndex} PageIndex */
11
14
  /** @typedef {import('./options.js').BookReaderOptions} BookReaderOptions */
12
15
  /** @typedef {import('./PageContainer.js').PageContainer} PageContainer */
16
+ /** @typedef {import('./ModeSmoothZoom').SmoothZoomable} SmoothZoomable */
13
17
 
18
+ /** @implements {SmoothZoomable} */
14
19
  export class Mode2Up {
15
20
  /**
16
21
  * @param {BookReader} br
@@ -27,6 +32,28 @@ export class Mode2Up {
27
32
 
28
33
  /** @type {{ [index: number]: PageContainer }} */
29
34
  this.pageContainers = {};
35
+
36
+ /** @type {ModeSmoothZoom} */
37
+ this.smoothZoomer = null;
38
+ this._scale = 1;
39
+ this.scaleCenter = { x: 0.5, y: 0.5 };
40
+
41
+ /** @type {ScrollClassAdder} */
42
+ this.scrollClassAdder = null;
43
+ }
44
+
45
+ get $container() {
46
+ return this.br.refs.$brContainer[0];
47
+ }
48
+ get $visibleWorld() {
49
+ return this.br.refs.$brTwoPageView?.[0];
50
+ }
51
+
52
+ get scale() { return this._scale; }
53
+ set scale(newVal) {
54
+ this.$visibleWorld.style.transform = `scale(${newVal})`;
55
+ this.updateViewportOnZoom(newVal, this._scale);
56
+ this._scale = newVal;
30
57
  }
31
58
 
32
59
  /**
@@ -42,18 +69,6 @@ export class Mode2Up {
42
69
  }
43
70
  }
44
71
 
45
- /**
46
- * @template T
47
- * @param {HTMLElement} element
48
- * @param {T} data
49
- * @param {function(HTMLElement, { data: T }): void} handler
50
- */
51
- setClickHandler(element, data, handler) {
52
- $(element).unbind('mouseup').bind('mouseup', data, function(e) {
53
- handler(this, e);
54
- });
55
- }
56
-
57
72
  /**
58
73
  * Draws book spread,
59
74
  * sets event handlers,
@@ -74,7 +89,6 @@ export class Mode2Up {
74
89
  .appendTo($twoPageViewEl);
75
90
 
76
91
  this.displayedIndices = [this.br.twoPage.currentIndexL, this.br.twoPage.currentIndexR];
77
- this.setMouseHandlers();
78
92
  this.br.displayedIndices = this.displayedIndices;
79
93
  this.br.updateToolbarZoom(this.br.reduce);
80
94
  this.br.trigger('pageChanged');
@@ -176,13 +190,22 @@ export class Mode2Up {
176
190
 
177
191
  // Add the two page view
178
192
  // $$$ Can we get everything set up and then append?
179
- const $twoPageViewEl = $('<div class="BRtwopageview"></div>');
180
- this.br.refs.$brTwoPageView = $twoPageViewEl;
193
+ this.br.refs.$brTwoPageView = this.br.refs.$brTwoPageView || $('<div class="BRtwopageview"></div>');
194
+ const $twoPageViewEl = this.br.refs.$brTwoPageView;
195
+ $twoPageViewEl.empty();
196
+ $twoPageViewEl[0].style.transformOrigin = '0 0';
181
197
  this.br.refs.$brContainer.append($twoPageViewEl);
182
198
 
183
199
  // Attaches to first child, so must come after we add the page view
184
- this.br.refs.$brContainer.dragscrollable({preventDefault:true});
185
- this.br.bindGestures(this.br.refs.$brContainer);
200
+ this.dragScrollable = this.dragScrollable || new DragScrollable(this.br.refs.$brContainer[0], {
201
+ preventDefault: true,
202
+ // Only handle mouse events; let browser/HammerJS handle touch
203
+ dragstart: 'mousedown',
204
+ dragcontinue: 'mousemove',
205
+ dragend: 'mouseup',
206
+ });
207
+
208
+ this.attachMouseHandlers();
186
209
 
187
210
  // $$$ calculate container size first
188
211
  this.br.refs?.$brTwoPageView.css(this.mainContainerCss);
@@ -219,13 +242,57 @@ export class Mode2Up {
219
242
 
220
243
  this.drawLeafs();
221
244
  this.br.updateToolbarZoom(this.br.reduce);
245
+ this.br.updateBrClasses();
222
246
 
223
- if (this.br.enableSearch) {
224
- this.br.removeSearchHilites();
225
- this.br.updateSearchHilites();
247
+ this.smoothZoomer = this.smoothZoomer || new ModeSmoothZoom(this);
248
+ this.smoothZoomer.attach();
249
+ if (!this.scrollClassAdder) {
250
+ this.scrollClassAdder = new ScrollClassAdder(this.$container, 'BRscrolling-active');
226
251
  }
252
+ this.scrollClassAdder.detach();
253
+ this.scrollClassAdder.element = this.$container;
254
+ this.scrollClassAdder.attach();
227
255
 
228
- this.br.updateBrClasses();
256
+ this.htmlDimensionsCacher = this.htmlDimensionsCacher || new HTMLDimensionsCacher(this.$container);
257
+ }
258
+
259
+ unprepare() {
260
+ // Mode2Up attaches these listeners to the main BR container, so we need to
261
+ // detach these or it will cause issues for the other modes.
262
+ this.smoothZoomer.detach();
263
+ this.scrollClassAdder.detach();
264
+ }
265
+
266
+ /**
267
+ * @param {number} newScale
268
+ * @param {number} oldScale
269
+ */
270
+ updateViewportOnZoom(newScale, oldScale) {
271
+ const container = this.br.refs.$brContainer[0];
272
+ const { scrollTop: T, scrollLeft: L } = container;
273
+ const W = this.htmlDimensionsCacher.clientWidth;
274
+ const H = this.htmlDimensionsCacher.clientHeight;
275
+
276
+ // Scale factor change
277
+ const F = newScale / oldScale;
278
+
279
+ // Where in the viewport the zoom is centered on
280
+ const XPOS = this.scaleCenter.x;
281
+ const YPOS = this.scaleCenter.y;
282
+ const oldCenter = {
283
+ x: L + XPOS * W,
284
+ y: T + YPOS * H,
285
+ };
286
+ const newCenter = {
287
+ x: F * oldCenter.x,
288
+ y: F * oldCenter.y,
289
+ };
290
+ container.scrollTop = newCenter.y - YPOS * H;
291
+ container.scrollLeft = newCenter.x - XPOS * W;
292
+
293
+ // Also update the visible page containers to load in highres if necessary
294
+ this.pageContainers[this.br.twoPage.currentIndexL]?.update({ reduce: this.br.reduce / newScale });
295
+ this.pageContainers[this.br.twoPage.currentIndexR]?.update({ reduce: this.br.reduce / newScale });
229
296
  }
230
297
 
231
298
  prunePageContainers() {
@@ -616,8 +683,6 @@ export class Mode2Up {
616
683
 
617
684
  $(this.br.leafEdgeTmp).animate({left: gutter}, this.br.flipSpeed, 'easeInSine');
618
685
 
619
- if (this.br.enableSearch) this.br.removeSearchHilites();
620
-
621
686
  this.pageContainers[leftLeaf].$container.animate({width: '0px'}, this.br.flipSpeed, 'easeInSine', () => {
622
687
 
623
688
  $(this.br.leafEdgeTmp).animate({left: `${gutter + newWidthR}px`}, this.br.flipSpeed, 'easeOutSine');
@@ -663,10 +728,6 @@ export class Mode2Up {
663
728
 
664
729
  this.resizeSpread();
665
730
 
666
- if (this.br.enableSearch) this.br.updateSearchHilites();
667
-
668
- this.setMouseHandlers();
669
-
670
731
  if (this.br.animationFinishedCallback) {
671
732
  this.br.animationFinishedCallback();
672
733
  this.br.animationFinishedCallback = null;
@@ -690,11 +751,11 @@ export class Mode2Up {
690
751
  /**
691
752
  * @param {PageIndex} index
692
753
  */
693
- createPageContainer(index, fetch = false) {
754
+ createPageContainer(index) {
694
755
  if (!this.pageContainers[index]) {
695
756
  this.pageContainers[index] = this.br._createPageContainer(index);
696
757
  }
697
- this.pageContainers[index].update({ reduce: this.br.reduce });
758
+ this.pageContainers[index].update({ reduce: this.br.reduce / this.scale });
698
759
  return this.pageContainers[index];
699
760
  }
700
761
 
@@ -778,8 +839,6 @@ export class Mode2Up {
778
839
  $(this.leafEdgeR).css({width: `${newLeafEdgeWidthR}px`, left: `${gutter + newWidthR}px` });
779
840
  const speed = this.br.flipSpeed;
780
841
 
781
- if (this.br.enableSearch) this.br.removeSearchHilites();
782
-
783
842
  $(this.br.leafEdgeTmp).animate({left: gutter}, speed, 'easeInSine');
784
843
  this.pageContainers[this.br.twoPage.currentIndexR].$container.animate({width: '0px'}, speed, 'easeInSine', () => {
785
844
  this.br.$('BRgutter').css({left: `${gutter - this.br.twoPage.bookSpineDivWidth * 0.5}px`});
@@ -814,10 +873,6 @@ export class Mode2Up {
814
873
 
815
874
  this.resizeSpread();
816
875
 
817
- if (this.br.enableSearch) this.br.updateSearchHilites();
818
-
819
- this.setMouseHandlers();
820
-
821
876
  if (this.br.animationFinishedCallback) {
822
877
  this.br.animationFinishedCallback();
823
878
  this.br.animationFinishedCallback = null;
@@ -837,40 +892,18 @@ export class Mode2Up {
837
892
  });
838
893
  }
839
894
 
840
- setMouseHandlers() {
841
- /**
842
- * @param {HTMLElement} element
843
- * @param {JQuery.MouseDownEvent<HTMLElement, { self: Mode2Up, direction: 'R' | 'L'}>} e
844
- */
845
- const handler = function(element, e) {
846
- if (e.which == 3) {
847
- // right click
848
- return !e.data.self.br.protected;
849
- }
850
- e.data.self.br.trigger(EVENTS.stop);
851
- e.data.self.br[e.data.direction === 'L' ? 'left' : 'right']();
852
-
853
- // Removed event handler for mouse movement, seems not to be needed
854
- /*
855
- // Changes per WEBDEV-2737
856
- BookReader: zoomed-in 2 page view, clicking page should change the page
857
- $(element)
858
- .mousemove(function() {
859
- e.preventDefault();
860
- })
861
- */
862
- }
895
+ attachMouseHandlers() {
896
+ this.br.refs.$brTwoPageView
897
+ .off('mouseup').on('mouseup', ev => {
898
+ if (ev.which == 3) {
899
+ // right click
900
+ return !this.br.protected;
901
+ }
863
902
 
864
- this.setClickHandler(
865
- this.pageContainers[this.br.twoPage.currentIndexR].$container[0],
866
- { self: this, direction: 'R' },
867
- handler
868
- );
869
- this.setClickHandler(
870
- this.pageContainers[this.br.twoPage.currentIndexL].$container[0],
871
- { self: this, direction: 'L' },
872
- handler
873
- );
903
+ const $page = $(ev.target).closest('.BRpagecontainer');
904
+ if ($page.data('side') == 'L') this.br.left();
905
+ else if ($page.data('side') == 'R') this.br.right();
906
+ });
874
907
  }
875
908
 
876
909
  /**
@@ -1248,7 +1281,7 @@ export class Mode2Up {
1248
1281
  ...this.heightCss,
1249
1282
  left: `${this.br.twoPage.gutter - this.br.twoPage.scaledWL}px`,
1250
1283
  width: `${this.br.twoPage.scaledWL}px`,
1251
- }
1284
+ };
1252
1285
  }
1253
1286
 
1254
1287
  /** Left side book thickness */
@@ -1269,7 +1302,7 @@ export class Mode2Up {
1269
1302
  ...this.heightCss,
1270
1303
  left: `${this.br.twoPage.gutter}px`,
1271
1304
  width: `${this.br.twoPage.scaledWR}px`,
1272
- }
1305
+ };
1273
1306
  }
1274
1307
 
1275
1308
  /** Right side book thickness */
@@ -0,0 +1,179 @@
1
+ // @ts-check
2
+ import Hammer from "hammerjs";
3
+ /** @typedef {import('./utils/HTMLDimensionsCacher.js').HTMLDimensionsCacher} HTMLDimensionsCacher */
4
+
5
+ /**
6
+ * @typedef {object} SmoothZoomable
7
+ * @property {HTMLElement} $container
8
+ * @property {HTMLElement} $visibleWorld
9
+ * @property {number} scale
10
+ * @property {{ x: number, y: number }} scaleCenter
11
+ * @property {HTMLDimensionsCacher} htmlDimensionsCacher
12
+ * @property {function(): void} [attachScrollListeners]
13
+ * @property {function(): void} [detachScrollListeners]
14
+ */
15
+
16
+ /** Manages pinch-zoom, ctrl-wheel, and trackpad pinch smooth zooming. */
17
+ export class ModeSmoothZoom {
18
+ /** @param {SmoothZoomable} mode */
19
+ constructor(mode) {
20
+ /** @type {SmoothZoomable} */
21
+ this.mode = mode;
22
+
23
+ /** Non-null when a scale has been enqueued/is being processed by the buffer function */
24
+ this.pinchMoveFrame = null;
25
+ /** Promise for the current/enqueued pinch move frame. Resolves when it is complete. */
26
+ this.pinchMoveFramePromise = Promise.resolve();
27
+ this.oldScale = 1;
28
+ /** @type {{ scale: number, center: { x: number, y: number }}} */
29
+ this.lastEvent = null;
30
+ this.attached = false;
31
+
32
+ /** @type {function(function(): void): any} */
33
+ this.bufferFn = window.requestAnimationFrame.bind(window);
34
+
35
+ // Hammer.js by default set userSelect to None; we don't want that!
36
+ // TODO: Is there any way to do this not globally on Hammer?
37
+ delete Hammer.defaults.cssProps.userSelect;
38
+ this.hammer = new Hammer.Manager(this.mode.$container, {
39
+ touchAction: "pan-x pan-y",
40
+ });
41
+
42
+ this.hammer.add(new Hammer.Pinch());
43
+ }
44
+
45
+ attach() {
46
+ if (this.attached) return;
47
+
48
+ this.attachCtrlZoom();
49
+
50
+ // GestureEvents work only on Safari; they interfere with Hammer,
51
+ // so block them.
52
+ this.mode.$container.addEventListener('gesturestart', this._preventEvent);
53
+ this.mode.$container.addEventListener('gesturechange', this._preventEvent);
54
+ this.mode.$container.addEventListener('gestureend', this._preventEvent);
55
+
56
+ // The pinch listeners
57
+ this.hammer.on("pinchstart", this._pinchStart);
58
+ this.hammer.on("pinchmove", this._pinchMove);
59
+ this.hammer.on("pinchend", this._pinchEnd);
60
+ this.hammer.on("pinchcancel", this._pinchCancel);
61
+
62
+ this.attached = true;
63
+ }
64
+
65
+ detach() {
66
+ this.detachCtrlZoom();
67
+
68
+ // GestureEvents work only on Safari; they interfere with Hammer,
69
+ // so block them.
70
+ this.mode.$container.removeEventListener('gesturestart', this._preventEvent);
71
+ this.mode.$container.removeEventListener('gesturechange', this._preventEvent);
72
+ this.mode.$container.removeEventListener('gestureend', this._preventEvent);
73
+
74
+ // The pinch listeners
75
+ this.hammer.off("pinchstart", this._pinchStart);
76
+ this.hammer.off("pinchmove", this._pinchMove);
77
+ this.hammer.off("pinchend", this._pinchEnd);
78
+ this.hammer.off("pinchcancel", this._pinchCancel);
79
+
80
+ this.attached = false;
81
+ }
82
+
83
+ /** @param {Event} ev */
84
+ _preventEvent = (ev) => {
85
+ ev.preventDefault();
86
+ return false;
87
+ }
88
+
89
+ _pinchStart = () => {
90
+ // Do this in case the pinchend hasn't fired yet.
91
+ this.oldScale = 1;
92
+ this.mode.$visibleWorld.classList.add("BRsmooth-zooming");
93
+ this.mode.$visibleWorld.style.willChange = "transform";
94
+ this.detachCtrlZoom();
95
+ this.mode.detachScrollListeners?.();
96
+ }
97
+
98
+ /** @param {{ scale: number, center: { x: number, y: number }}} e */
99
+ _pinchMove = async (e) => {
100
+ this.lastEvent = e;
101
+ if (!this.pinchMoveFrame) {
102
+ let pinchMoveFramePromiseRes = null;
103
+ this.pinchMoveFramePromise = new Promise(
104
+ (res) => (pinchMoveFramePromiseRes = res)
105
+ );
106
+
107
+ // Buffer these events; only update the scale when request animation fires
108
+ this.pinchMoveFrame = this.bufferFn(() => {
109
+ this.updateScaleCenter({
110
+ clientX: this.lastEvent.center.x,
111
+ clientY: this.lastEvent.center.y,
112
+ });
113
+ this.mode.scale *= this.lastEvent.scale / this.oldScale;
114
+ this.oldScale = this.lastEvent.scale;
115
+ this.pinchMoveFrame = null;
116
+ pinchMoveFramePromiseRes();
117
+ });
118
+ }
119
+ }
120
+
121
+ _pinchEnd = async () => {
122
+ // Want this to happen after the pinchMoveFrame,
123
+ // if one is in progress; otherwise setting oldScale
124
+ // messes up the transform.
125
+ await this.pinchMoveFramePromise;
126
+ this.mode.scaleCenter = { x: 0.5, y: 0.5 };
127
+ this.oldScale = 1;
128
+ this.mode.$visibleWorld.classList.remove("BRsmooth-zooming");
129
+ this.mode.$visibleWorld.style.willChange = "auto";
130
+ this.attachCtrlZoom();
131
+ this.mode.attachScrollListeners?.();
132
+ }
133
+
134
+ _pinchCancel = async () => {
135
+ // iOS fires pinchcancel ~randomly; it looks like it sometimes
136
+ // thinks the pinch becomes a pan, at which point it cancels?
137
+ await this._pinchEnd();
138
+ }
139
+
140
+ /** @private */
141
+ attachCtrlZoom() {
142
+ window.addEventListener("wheel", this._handleCtrlWheel, { passive: false });
143
+ }
144
+
145
+ /** @private */
146
+ detachCtrlZoom() {
147
+ window.removeEventListener("wheel", this._handleCtrlWheel);
148
+ }
149
+
150
+ /** @param {WheelEvent} ev **/
151
+ _handleCtrlWheel = (ev) => {
152
+ if (!ev.ctrlKey) return;
153
+ ev.preventDefault();
154
+ const zoomMultiplier =
155
+ // Zooming on macs was painfully slow; likely due to their better
156
+ // trackpads. Give them a higher zoom rate.
157
+ /Mac/i.test(navigator.platform)
158
+ ? 0.045
159
+ : // This worked well for me on Windows
160
+ 0.03;
161
+
162
+ // Zoom around the cursor
163
+ this.updateScaleCenter(ev);
164
+ this.mode.scale *= 1 - Math.sign(ev.deltaY) * zoomMultiplier;
165
+ }
166
+
167
+ /**
168
+ * @param {object} param0
169
+ * @param {number} param0.clientX
170
+ * @param {number} param0.clientY
171
+ */
172
+ updateScaleCenter({ clientX, clientY }) {
173
+ const bc = this.mode.htmlDimensionsCacher.boundingClientRect;
174
+ this.mode.scaleCenter = {
175
+ x: (clientX - bc.left) / this.mode.htmlDimensionsCacher.clientWidth,
176
+ y: (clientY - bc.top) / this.mode.htmlDimensionsCacher.clientHeight,
177
+ };
178
+ }
179
+ }
@@ -1,6 +1,7 @@
1
1
  // @ts-check
2
2
  import { notInArray, clamp } from './utils.js';
3
3
  import { EVENTS } from './events.js';
4
+ import { DragScrollable } from './DragScrollable.js';
4
5
  /** @typedef {import('../BookREader.js').default} BookReader */
5
6
  /** @typedef {import('./BookModel.js').PageIndex} PageIndex */
6
7
  /** @typedef {import('./BookModel.js').BookModel} BookModel */
@@ -234,20 +235,27 @@ export class ModeThumb {
234
235
  }
235
236
 
236
237
  /**
237
- * @param {1 | -1} direction
238
+ * @param {'in' | 'out'} direction
238
239
  */
239
240
  zoom(direction) {
240
241
  const oldColumns = this.br.thumbColumns;
241
242
  switch (direction) {
242
- case -1:
243
- this.br.thumbColumns += 1;
244
- break;
245
- case 1:
243
+ case 'in':
246
244
  this.br.thumbColumns -= 1;
247
245
  break;
246
+ case 'out':
247
+ this.br.thumbColumns += 1;
248
+ break;
249
+ default:
250
+ console.error(`Unsupported direction: ${direction}`);
248
251
  }
249
252
 
250
- this.br.thumbColumns = clamp(this.br.thumbColumns, 2, 8);
253
+ // Limit zoom in/out columns
254
+ this.br.thumbColumns = clamp(
255
+ this.br.thumbColumns,
256
+ this.br.options.thumbMinZoomColumns,
257
+ this.br.options.thumbMaxZoomColumns
258
+ );
251
259
 
252
260
  if (this.br.thumbColumns != oldColumns) {
253
261
  this.br.displayedRows = []; /* force a gallery redraw */
@@ -279,7 +287,7 @@ export class ModeThumb {
279
287
 
280
288
  this.br.refs.$brPageViewEl = $("<div class='BRpageview'></div>");
281
289
  this.br.refs.$brContainer.append(this.br.refs.$brPageViewEl);
282
- this.br.refs.$brContainer.dragscrollable({preventDefault:true});
290
+ this.dragScrollable = this.dragScrollable || new DragScrollable(this.br.refs.$brContainer[0], {preventDefault: true});
283
291
 
284
292
  this.br.bindGestures(this.br.refs.$brContainer);
285
293
 
@@ -330,7 +338,7 @@ export class ModeThumb {
330
338
  } else {
331
339
  this.br.animating = true;
332
340
  this.br.refs.$brContainer.stop(true)
333
- .animate({ scrollTop: leafTop }, 'fast', () => { this.br.animating = false });
341
+ .animate({ scrollTop: leafTop }, 'fast', () => { this.br.animating = false; });
334
342
  }
335
343
  }
336
344
  }
@@ -240,35 +240,6 @@ export class Navbar {
240
240
  return this.$nav;
241
241
  }
242
242
 
243
- /**
244
- * Initialize the navigation bar when embedded
245
- */
246
- initEmbed() {
247
- const { br } = this;
248
- // IA-specific
249
- const thisLink = (window.location + '')
250
- .replace('?ui=embed','')
251
- .replace('/stream/', '/details/')
252
- .replace('#', '/');
253
- const logoHtml = br.showLogo ? `<a class="logo" href="${br.logoURL}" target="_blank"></a>` : '';
254
-
255
- br.refs.$BRfooter = this.$root = $('<div class="BRfooter"></div>');
256
- br.refs.$BRnav = this.$nav = $(
257
- `<div class="BRnav BRnavEmbed">
258
- ${logoHtml}
259
- <span class="BRembedreturn">
260
- <a href="${thisLink}" target="_blank">${br.bookTitle}</a>
261
- </span>
262
- <span class="BRtoolbarbuttons">
263
- <button class="BRicon book_left"></button>
264
- <button class="BRicon book_right"></button>
265
- <button class="BRicon full"></button>
266
- </span>
267
- </div>`);
268
- this.$root.append(this.$nav);
269
- br.refs.$br.append(this.$root);
270
- }
271
-
272
243
  /**
273
244
  * Returns the textual representation of the current page for the navbar
274
245
  * @param {number} index
@@ -331,9 +302,9 @@ export function getNavPageNumHtml(index, numLeafs, pageNum, pageType, maxPageNum
331
302
 
332
303
  if (!pageIsAsserted) {
333
304
  const pageIndex = index + 1;
334
- return `Page (${pageIndex} of ${numLeafs})`; // Page (8 of 10)
305
+ return `(${pageIndex} of ${numLeafs})`; // Page (8 of 10)
335
306
  }
336
307
 
337
308
  const bookLengthLabel = maxPageNum ? ` of ${maxPageNum}` : '';
338
- return `Page ${pageNum}${bookLengthLabel}`;
309
+ return `${pageNum}${bookLengthLabel}`;
339
310
  }
@@ -46,11 +46,17 @@ export class PageContainer {
46
46
  const alreadyLoaded = this.imageCache.imageLoaded(this.page.index, reduce);
47
47
  const nextBestLoadedReduce = !alreadyLoaded && this.imageCache.getBestLoadedReduce(this.page.index, reduce);
48
48
 
49
- // Add the actual, highres image
49
+ // Create high res image
50
+ const $newImg = this.imageCache.image(this.page.index, reduce);
51
+
52
+ // Avoid removing/re-adding the image if it's already there
53
+ // This can be called quite a bit, so we need to be fast
54
+ if (this.$img?.[0].src == $newImg[0].src) {
55
+ return this;
56
+ }
57
+
50
58
  this.$img?.remove();
51
- this.$img = this.imageCache
52
- .image(this.page.index, reduce)
53
- .prependTo(this.$container);
59
+ this.$img = $newImg.prependTo(this.$container);
54
60
 
55
61
  const backgroundLayers = [];
56
62
  if (!alreadyLoaded) {
@@ -58,14 +64,14 @@ export class PageContainer {
58
64
  backgroundLayers.push(`url("${this.loadingImage}") center/20px no-repeat`);
59
65
  }
60
66
  if (nextBestLoadedReduce) {
61
- backgroundLayers.push(`url("${this.page.getURI(nextBestLoadedReduce, 0)}") center/100% no-repeat`);
67
+ backgroundLayers.push(`url("${this.page.getURI(nextBestLoadedReduce, 0)}") center/100% 100% no-repeat`);
62
68
  }
63
69
 
64
70
  if (!alreadyLoaded) {
65
71
  this.$img
66
72
  .css('background', backgroundLayers.join(','))
67
73
  .one('loadend', async (ev) => {
68
- $(ev.target).css({ 'background': '' })
74
+ $(ev.target).css({ 'background': '' });
69
75
  $(ev.target).parent().removeClass('BRpageloading');
70
76
  });
71
77
  }
@@ -73,3 +79,48 @@ export class PageContainer {
73
79
  return this;
74
80
  }
75
81
  }
82
+
83
+
84
+ /**
85
+ * @param {PageModel} page
86
+ * @param {string} className
87
+ */
88
+ export function createSVGPageLayer(page, className) {
89
+ const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
90
+ svg.setAttribute("xmlns", "http://www.w3.org/2000/svg");
91
+ svg.setAttribute("viewBox", `0 0 ${page.width} ${page.height}`);
92
+ svg.setAttribute('class', `BRPageLayer ${className}`);
93
+ svg.setAttribute('preserveAspectRatio', 'none');
94
+ return svg;
95
+ }
96
+
97
+ /**
98
+ * @param {{ l: number, r: number, b: number, t: number }} box
99
+ */
100
+ export function boxToSVGRect({ l: left, r: right, b: bottom, t: top }) {
101
+ const rect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
102
+ rect.setAttribute("x", left.toString());
103
+ rect.setAttribute("y", top.toString());
104
+ rect.setAttribute("width", (right - left).toString());
105
+ rect.setAttribute("height", (bottom - top).toString());
106
+ return rect;
107
+ }
108
+
109
+ /**
110
+ * @param {string} layerClass
111
+ * @param {Array<{ l: number, r: number, b: number, t: number }>} boxes
112
+ * @param {PageModel} page
113
+ * @param {HTMLElement} containerEl
114
+ */
115
+ export function renderBoxesInPageContainerLayer(layerClass, boxes, page, containerEl) {
116
+ const mountedSvg = containerEl.querySelector(`.${layerClass}`);
117
+ // Create the layer if it's not there
118
+ const svg = mountedSvg || createSVGPageLayer(page, layerClass);
119
+ if (!mountedSvg) {
120
+ // Insert after the image if the image is already loaded.
121
+ const imgEl = containerEl.querySelector('.BRpageimage');
122
+ if (imgEl) $(svg).insertAfter(imgEl);
123
+ else $(svg).prependTo(containerEl);
124
+ }
125
+ boxes.forEach(box => svg.appendChild(boxToSVGRect(box)));
126
+ }
@@ -18,7 +18,7 @@ export const Pow2ReduceSet = {
18
18
  decr(n) {
19
19
  return 2 ** (Math.log2(n) - 1);
20
20
  }
21
- }
21
+ };
22
22
 
23
23
  export const NAMED_REDUCE_SETS = {
24
24
  pow2: Pow2ReduceSet,