@internetarchive/bookreader 5.0.0-5 → 5.0.0-50-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 (260) hide show
  1. package/.eslintrc.js +17 -15
  2. package/.github/workflows/node.js.yml +77 -6
  3. package/.github/workflows/npm-publish.yml +6 -20
  4. package/.testcaferc.js +10 -0
  5. package/BookReader/BookReader.css +131 -339
  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 +1493 -0
  10. package/BookReader/ia-bookreader-bundle.js.LICENSE.txt +17 -0
  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/pause.svg +1 -1
  16. package/BookReader/icons/playback-speed.svg +1 -1
  17. package/BookReader/icons/read-aloud.svg +1 -1
  18. package/BookReader/icons/voice.svg +1 -0
  19. package/BookReader/images/BRicons.svg +2 -2
  20. package/BookReader/images/books_graphic.svg +1 -1
  21. package/BookReader/images/icon_book.svg +1 -1
  22. package/BookReader/images/icon_gear.svg +1 -1
  23. package/BookReader/images/icon_info.svg +1 -1
  24. package/BookReader/images/icon_playback-rate.svg +1 -1
  25. package/BookReader/images/icon_search_button.svg +1 -1
  26. package/BookReader/images/icon_share.svg +1 -1
  27. package/BookReader/images/icon_speaker.svg +1 -1
  28. package/BookReader/images/icon_speaker_open.svg +1 -1
  29. package/BookReader/images/marker_chap-off.svg +1 -1
  30. package/BookReader/images/marker_chap-on.svg +1 -1
  31. package/BookReader/images/marker_srch-on.svg +1 -1
  32. package/BookReader/jquery-3.js +2 -0
  33. package/BookReader/jquery-3.js.LICENSE.txt +24 -0
  34. package/BookReader/plugins/plugin.archive_analytics.js +1 -1
  35. package/BookReader/plugins/plugin.archive_analytics.js.map +1 -1
  36. package/BookReader/plugins/plugin.autoplay.js +1 -1
  37. package/BookReader/plugins/plugin.autoplay.js.map +1 -1
  38. package/BookReader/plugins/plugin.chapters.js +1 -1
  39. package/BookReader/plugins/plugin.chapters.js.map +1 -1
  40. package/BookReader/plugins/plugin.iframe.js +1 -1
  41. package/BookReader/plugins/plugin.iframe.js.map +1 -1
  42. package/BookReader/plugins/plugin.mobile_nav.js +1 -1
  43. package/BookReader/plugins/plugin.mobile_nav.js.map +1 -1
  44. package/BookReader/plugins/plugin.resume.js +1 -1
  45. package/BookReader/plugins/plugin.resume.js.map +1 -1
  46. package/BookReader/plugins/plugin.search.js +1 -1
  47. package/BookReader/plugins/plugin.search.js.map +1 -1
  48. package/BookReader/plugins/plugin.text_selection.js +1 -1
  49. package/BookReader/plugins/plugin.text_selection.js.map +1 -1
  50. package/BookReader/plugins/plugin.tts.js +1 -1
  51. package/BookReader/plugins/plugin.tts.js.map +1 -1
  52. package/BookReader/plugins/plugin.url.js +1 -1
  53. package/BookReader/plugins/plugin.url.js.map +1 -1
  54. package/BookReader/plugins/plugin.vendor-fullscreen.js +1 -1
  55. package/BookReader/plugins/plugin.vendor-fullscreen.js.map +1 -1
  56. package/BookReader/webcomponents-bundle.js +3 -0
  57. package/BookReader/webcomponents-bundle.js.LICENSE.txt +9 -0
  58. package/BookReader/webcomponents-bundle.js.map +1 -0
  59. package/BookReaderDemo/BookReaderDemo.css +14 -1
  60. package/BookReaderDemo/IADemoBr.js +148 -0
  61. package/BookReaderDemo/demo-advanced.html +2 -2
  62. package/BookReaderDemo/demo-autoplay.html +2 -1
  63. package/BookReaderDemo/demo-embed-iframe-src.html +2 -1
  64. package/BookReaderDemo/demo-fullscreen-mobile.html +2 -1
  65. package/BookReaderDemo/demo-fullscreen.html +2 -1
  66. package/BookReaderDemo/demo-iiif.html +2 -1
  67. package/BookReaderDemo/demo-internetarchive.html +84 -17
  68. package/BookReaderDemo/demo-multiple.html +2 -1
  69. package/BookReaderDemo/demo-preview-pages.html +2 -1
  70. package/BookReaderDemo/demo-simple.html +2 -1
  71. package/BookReaderDemo/demo-vendor-fullscreen.html +2 -1
  72. package/BookReaderDemo/ia-multiple-volumes-manifest.js +170 -0
  73. package/BookReaderDemo/immersion-1up.html +2 -1
  74. package/BookReaderDemo/immersion-mode.html +2 -1
  75. package/BookReaderDemo/toggle_controls.html +2 -1
  76. package/BookReaderDemo/view_mode.html +2 -1
  77. package/BookReaderDemo/viewmode-cycle.html +2 -3
  78. package/CHANGELOG.md +202 -0
  79. package/README.md +14 -1
  80. package/babel.config.js +18 -0
  81. package/codecov.yml +6 -0
  82. package/index.html +3 -0
  83. package/jsconfig.json +19 -0
  84. package/package.json +66 -56
  85. package/renovate.json +52 -0
  86. package/scripts/preversion.js +4 -1
  87. package/src/BookNavigator/assets/bookmark-colors.js +1 -1
  88. package/src/BookNavigator/assets/button-base.js +9 -2
  89. package/src/BookNavigator/assets/ia-logo.js +17 -0
  90. package/src/BookNavigator/assets/icon_checkmark.js +1 -1
  91. package/src/BookNavigator/assets/icon_close.js +1 -1
  92. package/src/BookNavigator/assets/icon_sort_asc.js +5 -0
  93. package/src/BookNavigator/assets/icon_sort_desc.js +5 -0
  94. package/src/BookNavigator/assets/icon_sort_neutral.js +5 -0
  95. package/src/BookNavigator/assets/icon_volumes.js +11 -0
  96. package/src/BookNavigator/book-navigator.js +583 -0
  97. package/src/BookNavigator/bookmarks/bookmark-button.js +3 -2
  98. package/src/BookNavigator/bookmarks/bookmark-edit.js +3 -4
  99. package/src/BookNavigator/bookmarks/bookmarks-list.js +2 -3
  100. package/src/BookNavigator/bookmarks/bookmarks-loginCTA.js +3 -8
  101. package/src/BookNavigator/bookmarks/bookmarks-provider.js +21 -8
  102. package/src/BookNavigator/bookmarks/ia-bookmarks.js +102 -66
  103. package/src/BookNavigator/delete-modal-actions.js +1 -1
  104. package/src/BookNavigator/downloads/downloads-provider.js +36 -21
  105. package/src/BookNavigator/downloads/downloads.js +41 -25
  106. package/src/BookNavigator/search/a-search-result.js +18 -13
  107. package/src/BookNavigator/search/search-provider.js +80 -28
  108. package/src/BookNavigator/search/search-results.js +10 -18
  109. package/src/BookNavigator/sharing.js +27 -0
  110. package/src/BookNavigator/visual-adjustments/visual-adjustments-provider.js +11 -10
  111. package/src/BookNavigator/visual-adjustments/visual-adjustments.js +3 -3
  112. package/src/BookNavigator/volumes/volumes-provider.js +114 -0
  113. package/src/BookNavigator/volumes/volumes.js +188 -0
  114. package/src/BookReader/BookModel.js +0 -29
  115. package/src/BookReader/DebugConsole.js +3 -3
  116. package/src/BookReader/DragScrollable.js +233 -0
  117. package/src/BookReader/Mode1Up.js +51 -351
  118. package/src/BookReader/Mode1UpLit.js +441 -0
  119. package/src/BookReader/Mode2Up.js +120 -105
  120. package/src/BookReader/ModeSmoothZoom.js +179 -0
  121. package/src/BookReader/ModeThumb.js +17 -11
  122. package/src/BookReader/Navbar/Navbar.js +10 -36
  123. package/src/BookReader/PageContainer.js +69 -6
  124. package/src/BookReader/ReduceSet.js +1 -1
  125. package/src/BookReader/Toolbar/Toolbar.js +10 -37
  126. package/src/BookReader/options.js +10 -0
  127. package/src/BookReader/utils/HTMLDimensionsCacher.js +44 -0
  128. package/src/BookReader/utils/ScrollClassAdder.js +31 -0
  129. package/src/BookReader/utils.js +92 -13
  130. package/src/BookReader.js +431 -620
  131. package/src/assets/icons/close-circle-dark.svg +1 -0
  132. package/src/assets/icons/magnify-minus.svg +3 -7
  133. package/src/assets/icons/magnify-plus.svg +3 -7
  134. package/src/assets/icons/voice.svg +1 -0
  135. package/src/css/BookReader.scss +0 -12
  136. package/src/css/_BRComponent.scss +1 -1
  137. package/src/css/_BRmain.scss +19 -24
  138. package/src/css/_BRnav.scss +4 -26
  139. package/src/css/_BRpages.scss +35 -0
  140. package/src/css/_BRsearch.scss +25 -216
  141. package/src/css/_TextSelection.scss +14 -17
  142. package/src/css/_colorbox.scss +2 -2
  143. package/src/css/_controls.scss +17 -5
  144. package/src/css/_icons.scss +6 -0
  145. package/src/ia-bookreader/ia-bookreader.js +224 -0
  146. package/src/plugins/plugin.autoplay.js +4 -4
  147. package/src/plugins/plugin.chapters.js +28 -35
  148. package/src/plugins/plugin.mobile_nav.js +11 -10
  149. package/src/plugins/plugin.resume.js +3 -3
  150. package/src/plugins/plugin.text_selection.js +26 -39
  151. package/src/plugins/plugin.vendor-fullscreen.js +4 -4
  152. package/src/plugins/search/plugin.search.js +174 -116
  153. package/src/plugins/search/view.js +63 -179
  154. package/src/plugins/tts/AbstractTTSEngine.js +46 -37
  155. package/src/plugins/tts/FestivalTTSEngine.js +13 -14
  156. package/src/plugins/tts/PageChunk.js +15 -21
  157. package/src/plugins/tts/PageChunkIterator.js +8 -12
  158. package/src/plugins/tts/WebTTSEngine.js +66 -69
  159. package/src/plugins/tts/plugin.tts.js +92 -109
  160. package/src/plugins/tts/utils.js +0 -9
  161. package/src/plugins/url/UrlPlugin.js +184 -0
  162. package/src/plugins/{plugin.url.js → url/plugin.url.js} +28 -6
  163. package/src/util/manifestGenerator.js +0 -0
  164. package/tests/e2e/README.md +37 -0
  165. package/tests/e2e/autoplay.test.js +2 -2
  166. package/tests/e2e/base.test.js +7 -7
  167. package/tests/e2e/helpers/base.js +9 -3
  168. package/tests/e2e/helpers/debug.js +1 -1
  169. package/tests/e2e/helpers/desktopSearch.js +14 -13
  170. package/tests/e2e/helpers/mobileSearch.js +3 -3
  171. package/tests/e2e/helpers/params.js +17 -0
  172. package/tests/e2e/models/Navigation.js +13 -4
  173. package/tests/e2e/rightToLeft.test.js +4 -5
  174. package/tests/e2e/viewmode.test.js +38 -33
  175. package/tests/jest/BookNavigator/book-navigator.test.js +634 -0
  176. package/tests/jest/BookNavigator/bookmarks/bookmark-button.test.js +43 -0
  177. package/tests/{karma → jest}/BookNavigator/bookmarks/bookmark-edit.test.js +25 -26
  178. package/tests/{karma → jest}/BookNavigator/bookmarks/bookmarks-list.test.js +41 -42
  179. package/tests/jest/BookNavigator/bookmarks/ia-bookmarks.test.js +45 -0
  180. package/tests/jest/BookNavigator/downloads/downloads-provider.test.js +67 -0
  181. package/tests/jest/BookNavigator/downloads/downloads.test.js +53 -0
  182. package/tests/jest/BookNavigator/search/search-provider.test.js +167 -0
  183. package/tests/{karma/BookNavigator → jest/BookNavigator/search}/search-results.test.js +102 -58
  184. package/tests/jest/BookNavigator/sharing/sharing-provider.test.js +49 -0
  185. package/tests/jest/BookNavigator/visual-adjustments.test.js +200 -0
  186. package/tests/jest/BookNavigator/volumes/volumes-provider.test.js +184 -0
  187. package/tests/jest/BookNavigator/volumes/volumes.test.js +97 -0
  188. package/tests/{BookReader → jest/BookReader}/BookModel.test.js +34 -14
  189. package/tests/jest/BookReader/BookReaderPublicFunctions.test.js +176 -0
  190. package/tests/{BookReader → jest/BookReader}/DebugConsole.test.js +1 -1
  191. package/tests/{BookReader → jest/BookReader}/ImageCache.test.js +4 -4
  192. package/tests/jest/BookReader/Mode1UpLit.test.js +92 -0
  193. package/tests/{BookReader → jest/BookReader}/Mode2Up.test.js +36 -15
  194. package/tests/jest/BookReader/ModeSmoothZoom.test.js +149 -0
  195. package/tests/jest/BookReader/ModeThumb.test.js +71 -0
  196. package/tests/{BookReader → jest/BookReader}/Navbar/Navbar.test.js +7 -7
  197. package/tests/{BookReader → jest/BookReader}/PageContainer.test.js +88 -6
  198. package/tests/{BookReader → jest/BookReader}/ReduceSet.test.js +1 -1
  199. package/tests/{BookReader → jest/BookReader}/Toolbar/Toolbar.test.js +2 -2
  200. package/tests/jest/BookReader/utils/HTMLDimensionsCacher.test.js +59 -0
  201. package/tests/jest/BookReader/utils/ScrollClassAdder.test.js +49 -0
  202. package/tests/{BookReader → jest/BookReader}/utils/classes.test.js +1 -1
  203. package/tests/jest/BookReader/utils.test.js +186 -0
  204. package/tests/jest/BookReader.keyboard.test.js +190 -0
  205. package/tests/{BookReader.options.test.js → jest/BookReader.options.test.js} +9 -1
  206. package/tests/{BookReader.test.js → jest/BookReader.test.js} +18 -37
  207. package/tests/{plugins → jest/plugins}/plugin.archive_analytics.test.js +2 -2
  208. package/tests/{plugins → jest/plugins}/plugin.autoplay.test.js +4 -4
  209. package/tests/{plugins → jest/plugins}/plugin.chapters.test.js +10 -11
  210. package/tests/{plugins → jest/plugins}/plugin.iframe.test.js +2 -2
  211. package/tests/{plugins → jest/plugins}/plugin.mobile_nav.test.js +5 -5
  212. package/tests/{plugins → jest/plugins}/plugin.resume.test.js +3 -3
  213. package/tests/{plugins → jest/plugins}/plugin.text_selection.test.js +39 -47
  214. package/tests/{plugins → jest/plugins}/plugin.vendor-fullscreen.test.js +2 -2
  215. package/tests/{plugins → jest/plugins}/search/plugin.search.test.js +63 -47
  216. package/tests/{plugins → jest/plugins}/search/plugin.search.view.test.js +35 -6
  217. package/tests/{plugins → jest/plugins}/tts/AbstractTTSEngine.test.js +9 -9
  218. package/tests/{plugins → jest/plugins}/tts/FestivalTTSEngine.test.js +4 -4
  219. package/tests/{plugins → jest/plugins}/tts/PageChunk.test.js +1 -1
  220. package/tests/{plugins → jest/plugins}/tts/PageChunkIterator.test.js +3 -3
  221. package/tests/{plugins → jest/plugins}/tts/WebTTSEngine.test.js +1 -1
  222. package/tests/{plugins → jest/plugins}/tts/utils.test.js +3 -28
  223. package/tests/jest/plugins/url/UrlPlugin.test.js +190 -0
  224. package/tests/{plugins → jest/plugins/url}/plugin.url.test.js +33 -14
  225. package/tests/{util → jest/util}/browserSniffing.test.js +1 -1
  226. package/tests/{util → jest/util}/docCookies.test.js +1 -1
  227. package/tests/{util → jest/util}/strings.test.js +1 -1
  228. package/tests/{utils.js → jest/utils.js} +38 -0
  229. package/webpack.config.js +11 -5
  230. package/.babelrc +0 -12
  231. package/.dependabot/config.yml +0 -6
  232. package/.testcaferc.json +0 -5
  233. package/BookReader/bookreader-component-bundle.js +0 -1450
  234. package/BookReader/bookreader-component-bundle.js.LICENSE.txt +0 -38
  235. package/BookReader/bookreader-component-bundle.js.map +0 -1
  236. package/BookReader/jquery-1.10.1.js +0 -2
  237. package/BookReader/jquery-1.10.1.js.LICENSE.txt +0 -24
  238. package/BookReader/plugins/plugin.menu_toggle.js +0 -2
  239. package/BookReader/plugins/plugin.menu_toggle.js.map +0 -1
  240. package/BookReaderDemo/bookreader-template-bundle.js +0 -7178
  241. package/BookReaderDemo/demo-plugin-menu-toggle.html +0 -34
  242. package/karma.conf.js +0 -23
  243. package/src/BookNavigator/BookModel.js +0 -14
  244. package/src/BookNavigator/BookNavigator.js +0 -438
  245. package/src/BookNavigator/assets/book-loader.js +0 -27
  246. package/src/BookNavigator/br-fullscreen-mgr.js +0 -83
  247. package/src/BookReaderComponent/BookReaderComponent.js +0 -112
  248. package/src/ItemNavigator/ItemNavigator.js +0 -372
  249. package/src/ItemNavigator/providers/sharing.js +0 -29
  250. package/src/Layers/sharing/sharing-provider.js +0 -22
  251. package/src/dragscrollable-br.js +0 -261
  252. package/src/plugins/menu_toggle/plugin.menu_toggle.js +0 -324
  253. package/src/plugins/plugin.bookmarks.js +0 -50
  254. package/tests/BookReader/BookReaderPublicFunctions.test.js +0 -171
  255. package/tests/BookReader/Mode1Up.test.js +0 -164
  256. package/tests/BookReader/utils.test.js +0 -109
  257. package/tests/e2e/ia-production/ia-prod-base.js +0 -17
  258. package/tests/karma/BookNavigator/book-navigator.test.js +0 -132
  259. package/tests/karma/BookNavigator/visual-adjustments.test.js +0 -201
  260. 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,9 +89,7 @@ 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
- this.br.updateToolbarZoom(this.br.reduce);
80
93
  this.br.trigger('pageChanged');
81
94
  }
82
95
 
@@ -104,7 +117,7 @@ export class Mode2Up {
104
117
 
105
118
  // Prepare view with new center to minimize visual glitches
106
119
  const drawNewSpread = true;
107
- this.prepareTwoPageView(oldCenter.percentageX, oldCenter.percentageY, drawNewSpread);
120
+ this.prepare(oldCenter.percentageX, oldCenter.percentageY, drawNewSpread);
108
121
  }
109
122
 
110
123
  /**
@@ -136,7 +149,7 @@ export class Mode2Up {
136
149
  * @param {number} centerPercentageY
137
150
  * @param {Boolean} drawNewSpread
138
151
  */
139
- prepareTwoPageView(centerPercentageX, centerPercentageY, drawNewSpread = false) {
152
+ prepare(centerPercentageX, centerPercentageY, drawNewSpread = false) {
140
153
  // Some decisions about two page view:
141
154
  //
142
155
  // Both pages will be displayed at the same height, even if they were different physical/scanned
@@ -176,13 +189,22 @@ export class Mode2Up {
176
189
 
177
190
  // Add the two page view
178
191
  // $$$ Can we get everything set up and then append?
179
- const $twoPageViewEl = $('<div class="BRtwopageview"></div>');
180
- this.br.refs.$brTwoPageView = $twoPageViewEl;
192
+ this.br.refs.$brTwoPageView = this.br.refs.$brTwoPageView || $('<div class="BRtwopageview"></div>');
193
+ const $twoPageViewEl = this.br.refs.$brTwoPageView;
194
+ $twoPageViewEl.empty();
195
+ $twoPageViewEl[0].style.transformOrigin = '0 0';
181
196
  this.br.refs.$brContainer.append($twoPageViewEl);
182
197
 
183
198
  // 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);
199
+ this.dragScrollable = this.dragScrollable || new DragScrollable(this.br.refs.$brContainer[0], {
200
+ preventDefault: true,
201
+ // Only handle mouse events; let browser/HammerJS handle touch
202
+ dragstart: 'mousedown',
203
+ dragcontinue: 'mousemove',
204
+ dragend: 'mouseup',
205
+ });
206
+
207
+ this.attachMouseHandlers();
186
208
 
187
209
  // $$$ calculate container size first
188
210
  this.br.refs?.$brTwoPageView.css(this.mainContainerCss);
@@ -218,14 +240,57 @@ export class Mode2Up {
218
240
  this.br.displayedIndices = [];
219
241
 
220
242
  this.drawLeafs();
221
- this.br.updateToolbarZoom(this.br.reduce);
243
+ this.br.updateBrClasses();
222
244
 
223
- if (this.br.enableSearch) {
224
- this.br.removeSearchHilites();
225
- this.br.updateSearchHilites();
245
+ this.smoothZoomer = this.smoothZoomer || new ModeSmoothZoom(this);
246
+ this.smoothZoomer.attach();
247
+ if (!this.scrollClassAdder) {
248
+ this.scrollClassAdder = new ScrollClassAdder(this.$container, 'BRscrolling-active');
226
249
  }
250
+ this.scrollClassAdder.detach();
251
+ this.scrollClassAdder.element = this.$container;
252
+ this.scrollClassAdder.attach();
227
253
 
228
- this.br.updateBrClasses();
254
+ this.htmlDimensionsCacher = this.htmlDimensionsCacher || new HTMLDimensionsCacher(this.$container);
255
+ }
256
+
257
+ unprepare() {
258
+ // Mode2Up attaches these listeners to the main BR container, so we need to
259
+ // detach these or it will cause issues for the other modes.
260
+ this.smoothZoomer.detach();
261
+ this.scrollClassAdder.detach();
262
+ }
263
+
264
+ /**
265
+ * @param {number} newScale
266
+ * @param {number} oldScale
267
+ */
268
+ updateViewportOnZoom(newScale, oldScale) {
269
+ const container = this.br.refs.$brContainer[0];
270
+ const { scrollTop: T, scrollLeft: L } = container;
271
+ const W = this.htmlDimensionsCacher.clientWidth;
272
+ const H = this.htmlDimensionsCacher.clientHeight;
273
+
274
+ // Scale factor change
275
+ const F = newScale / oldScale;
276
+
277
+ // Where in the viewport the zoom is centered on
278
+ const XPOS = this.scaleCenter.x;
279
+ const YPOS = this.scaleCenter.y;
280
+ const oldCenter = {
281
+ x: L + XPOS * W,
282
+ y: T + YPOS * H,
283
+ };
284
+ const newCenter = {
285
+ x: F * oldCenter.x,
286
+ y: F * oldCenter.y,
287
+ };
288
+ container.scrollTop = newCenter.y - YPOS * H;
289
+ container.scrollLeft = newCenter.x - XPOS * W;
290
+
291
+ // Also update the visible page containers to load in highres if necessary
292
+ this.pageContainers[this.br.twoPage.currentIndexL]?.update({ reduce: this.br.reduce / newScale });
293
+ this.pageContainers[this.br.twoPage.currentIndexR]?.update({ reduce: this.br.reduce / newScale });
229
294
  }
230
295
 
231
296
  prunePageContainers() {
@@ -324,7 +389,7 @@ export class Mode2Up {
324
389
 
325
390
  // Leaf edges
326
391
  this.br.twoPage.edgeWidth = spreadSize.totalLeafEdgeWidth; // The combined width of both edges
327
- this.br.twoPage.leafEdgeWidthL = this.br.leafEdgeWidth(this.br.twoPage.currentIndexL);
392
+ this.br.twoPage.leafEdgeWidthL = this.leafEdgeWidth(this.br.twoPage.currentIndexL);
328
393
  this.br.twoPage.leafEdgeWidthR = this.br.twoPage.edgeWidth - this.br.twoPage.leafEdgeWidthL;
329
394
 
330
395
 
@@ -461,21 +526,6 @@ export class Mode2Up {
461
526
  return spreadSize.reduce;
462
527
  }
463
528
 
464
- /**
465
- * Returns true if the pages extend past the edge of the view
466
- * @deprecated slated for deprecation by v5.0.0
467
- * @return {boolean}
468
- */
469
- isZoomedIn() {
470
- let isZoomedIn = false;
471
- if (this.br.twoPage.autofit != 'auto') {
472
- if (this.br.reduce < this.getAutofitReduce()) {
473
- isZoomedIn = true;
474
- }
475
- }
476
- return isZoomedIn;
477
- }
478
-
479
529
  calculateReductionFactors() {
480
530
  this.br.twoPage.reductionFactors = this.br.reductionFactors.concat([
481
531
  {
@@ -486,14 +536,6 @@ export class Mode2Up {
486
536
  this.br.twoPage.reductionFactors.sort(this.br._reduceSort);
487
537
  }
488
538
 
489
- /**
490
- * Set the cursor for two page view
491
- * @deprecated Since version 4.3.3. Will be deleted in version 5.0
492
- */
493
- setCursor() {
494
- console.warn('Call to deprecated method, Mode2Up.setCursor. No-op.');
495
- }
496
-
497
539
  /**
498
540
  * @param {Number|null} index to flip back one spread, pass index=null
499
541
  */
@@ -517,7 +559,7 @@ export class Mode2Up {
517
559
  if (prev.pageSide == 'R') index--;
518
560
  }
519
561
 
520
- this.br.updateNavIndexThrottled(index);
562
+ this.br._components.navbar.updateNavIndexThrottled(index);
521
563
 
522
564
  const previousIndices = this.book.getSpreadIndices(index);
523
565
 
@@ -547,8 +589,8 @@ export class Mode2Up {
547
589
  this.br.refs.$brContainer.addClass("BRpageFlipping");
548
590
  const leftLeaf = this.br.twoPage.currentIndexL;
549
591
 
550
- const oldLeafEdgeWidthL = this.br.leafEdgeWidth(this.br.twoPage.currentIndexL);
551
- const newLeafEdgeWidthL = this.br.leafEdgeWidth(newIndexL);
592
+ const oldLeafEdgeWidthL = this.leafEdgeWidth(this.br.twoPage.currentIndexL);
593
+ const newLeafEdgeWidthL = this.leafEdgeWidth(newIndexL);
552
594
  const leafEdgeTmpW = oldLeafEdgeWidthL - newLeafEdgeWidthL;
553
595
 
554
596
  const currWidthL = this.getPageWidth(leftLeaf);
@@ -616,8 +658,6 @@ export class Mode2Up {
616
658
 
617
659
  $(this.br.leafEdgeTmp).animate({left: gutter}, this.br.flipSpeed, 'easeInSine');
618
660
 
619
- if (this.br.enableSearch) this.br.removeSearchHilites();
620
-
621
661
  this.pageContainers[leftLeaf].$container.animate({width: '0px'}, this.br.flipSpeed, 'easeInSine', () => {
622
662
 
623
663
  $(this.br.leafEdgeTmp).animate({left: `${gutter + newWidthR}px`}, this.br.flipSpeed, 'easeOutSine');
@@ -663,10 +703,6 @@ export class Mode2Up {
663
703
 
664
704
  this.resizeSpread();
665
705
 
666
- if (this.br.enableSearch) this.br.updateSearchHilites();
667
-
668
- this.setMouseHandlers();
669
-
670
706
  if (this.br.animationFinishedCallback) {
671
707
  this.br.animationFinishedCallback();
672
708
  this.br.animationFinishedCallback = null;
@@ -690,11 +726,11 @@ export class Mode2Up {
690
726
  /**
691
727
  * @param {PageIndex} index
692
728
  */
693
- createPageContainer(index, fetch = false) {
729
+ createPageContainer(index) {
694
730
  if (!this.pageContainers[index]) {
695
731
  this.pageContainers[index] = this.br._createPageContainer(index);
696
732
  }
697
- this.pageContainers[index].update({ reduce: this.br.reduce });
733
+ this.pageContainers[index].update({ reduce: this.br.reduce / this.scale });
698
734
  return this.pageContainers[index];
699
735
  }
700
736
 
@@ -721,7 +757,7 @@ export class Mode2Up {
721
757
  }
722
758
  if (index > this.br.lastDisplayableIndex()) return;
723
759
 
724
- this.br.updateNavIndexThrottled(index);
760
+ this.br._components.navbar.updateNavIndexThrottled(index);
725
761
 
726
762
  this.br.animating = true;
727
763
 
@@ -747,9 +783,9 @@ export class Mode2Up {
747
783
  flipRightToLeft(newIndexL, newIndexR) {
748
784
  this.br.refs.$brContainer.addClass("BRpageFlipping");
749
785
 
750
- const oldLeafEdgeWidthL = this.br.leafEdgeWidth(this.br.twoPage.currentIndexL);
786
+ const oldLeafEdgeWidthL = this.leafEdgeWidth(this.br.twoPage.currentIndexL);
751
787
  const oldLeafEdgeWidthR = this.br.twoPage.edgeWidth - oldLeafEdgeWidthL;
752
- const newLeafEdgeWidthL = this.br.leafEdgeWidth(newIndexL);
788
+ const newLeafEdgeWidthL = this.leafEdgeWidth(newIndexL);
753
789
  const newLeafEdgeWidthR = this.br.twoPage.edgeWidth - newLeafEdgeWidthL;
754
790
 
755
791
  const leafEdgeTmpW = oldLeafEdgeWidthR - newLeafEdgeWidthR;
@@ -778,12 +814,17 @@ export class Mode2Up {
778
814
  $(this.leafEdgeR).css({width: `${newLeafEdgeWidthR}px`, left: `${gutter + newWidthR}px` });
779
815
  const speed = this.br.flipSpeed;
780
816
 
781
- if (this.br.enableSearch) this.br.removeSearchHilites();
782
-
783
817
  $(this.br.leafEdgeTmp).animate({left: gutter}, speed, 'easeInSine');
784
818
  this.pageContainers[this.br.twoPage.currentIndexR].$container.animate({width: '0px'}, speed, 'easeInSine', () => {
785
819
  this.br.$('BRgutter').css({left: `${gutter - this.br.twoPage.bookSpineDivWidth * 0.5}px`});
786
820
  $(this.br.leafEdgeTmp).animate({left: `${gutter - newWidthL - leafEdgeTmpW}px`}, speed, 'easeOutSine');
821
+
822
+ // Ensure the new left leaf is right-positioned before animating its width.
823
+ // Otherwise, it animates in the wrong direction.
824
+ this.pageContainers[newIndexL].$container.css({
825
+ right: `${$twoPageViewEl.prop('clientWidth') - gutter}px`,
826
+ left: ''
827
+ });
787
828
  this.pageContainers[newIndexL].$container.animate({width: `${newWidthL}px`}, speed, 'easeOutSine', () => {
788
829
  this.pageContainers[newIndexR].$container.css('zIndex', 2);
789
830
 
@@ -814,10 +855,6 @@ export class Mode2Up {
814
855
 
815
856
  this.resizeSpread();
816
857
 
817
- if (this.br.enableSearch) this.br.updateSearchHilites();
818
-
819
- this.setMouseHandlers();
820
-
821
858
  if (this.br.animationFinishedCallback) {
822
859
  this.br.animationFinishedCallback();
823
860
  this.br.animationFinishedCallback = null;
@@ -837,40 +874,18 @@ export class Mode2Up {
837
874
  });
838
875
  }
839
876
 
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
- }
877
+ attachMouseHandlers() {
878
+ this.br.refs.$brTwoPageView
879
+ .off('mouseup').on('mouseup', ev => {
880
+ if (ev.which == 3) {
881
+ // right click
882
+ return !this.br.protected;
883
+ }
863
884
 
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
- );
885
+ const $page = $(ev.target).closest('.BRpagecontainer');
886
+ if ($page.data('side') == 'L') this.br.left();
887
+ else if ($page.data('side') == 'R') this.br.right();
888
+ });
874
889
  }
875
890
 
876
891
  /**
@@ -1248,7 +1263,7 @@ export class Mode2Up {
1248
1263
  ...this.heightCss,
1249
1264
  left: `${this.br.twoPage.gutter - this.br.twoPage.scaledWL}px`,
1250
1265
  width: `${this.br.twoPage.scaledWL}px`,
1251
- }
1266
+ };
1252
1267
  }
1253
1268
 
1254
1269
  /** Left side book thickness */
@@ -1269,7 +1284,7 @@ export class Mode2Up {
1269
1284
  ...this.heightCss,
1270
1285
  left: `${this.br.twoPage.gutter}px`,
1271
1286
  width: `${this.br.twoPage.scaledWR}px`,
1272
- }
1287
+ };
1273
1288
  }
1274
1289
 
1275
1290
  /** 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 */
@@ -176,7 +177,7 @@ export class ModeThumb {
176
177
  // shift viewModeOrder after clicking on thumbsnail leaf
177
178
  const nextModeID = this.br.viewModeOrder.shift();
178
179
  this.br.viewModeOrder.push(nextModeID);
179
- this.br.updateViewModeButton($('.viewmode'), 'twopg', 'Two-page view');
180
+ this.br._components.navbar.updateViewModeButton($('.viewmode'), 'twopg', 'Two-page view');
180
181
 
181
182
  this.br.trigger(EVENTS.fragmentChange);
182
183
  event.stopPropagation();
@@ -214,8 +215,6 @@ export class ModeThumb {
214
215
 
215
216
  // highlight current page
216
217
  this.br.$('.pagediv' + this.br.currentIndex()).addClass('BRpagedivthumb_highlight');
217
-
218
- this.br.updateToolbarZoom(this.br.reduce);
219
218
  }
220
219
 
221
220
  /**
@@ -234,20 +233,27 @@ export class ModeThumb {
234
233
  }
235
234
 
236
235
  /**
237
- * @param {1 | -1} direction
236
+ * @param {'in' | 'out'} direction
238
237
  */
239
238
  zoom(direction) {
240
239
  const oldColumns = this.br.thumbColumns;
241
240
  switch (direction) {
242
- case -1:
243
- this.br.thumbColumns += 1;
244
- break;
245
- case 1:
241
+ case 'in':
246
242
  this.br.thumbColumns -= 1;
247
243
  break;
244
+ case 'out':
245
+ this.br.thumbColumns += 1;
246
+ break;
247
+ default:
248
+ console.error(`Unsupported direction: ${direction}`);
248
249
  }
249
250
 
250
- this.br.thumbColumns = clamp(this.br.thumbColumns, 2, 8);
251
+ // Limit zoom in/out columns
252
+ this.br.thumbColumns = clamp(
253
+ this.br.thumbColumns,
254
+ this.br.options.thumbMinZoomColumns,
255
+ this.br.options.thumbMaxZoomColumns
256
+ );
251
257
 
252
258
  if (this.br.thumbColumns != oldColumns) {
253
259
  this.br.displayedRows = []; /* force a gallery redraw */
@@ -279,7 +285,7 @@ export class ModeThumb {
279
285
 
280
286
  this.br.refs.$brPageViewEl = $("<div class='BRpageview'></div>");
281
287
  this.br.refs.$brContainer.append(this.br.refs.$brPageViewEl);
282
- this.br.refs.$brContainer.dragscrollable({preventDefault:true});
288
+ this.dragScrollable = this.dragScrollable || new DragScrollable(this.br.refs.$brContainer[0], {preventDefault: true});
283
289
 
284
290
  this.br.bindGestures(this.br.refs.$brContainer);
285
291
 
@@ -330,7 +336,7 @@ export class ModeThumb {
330
336
  } else {
331
337
  this.br.animating = true;
332
338
  this.br.refs.$brContainer.stop(true)
333
- .animate({ scrollTop: leafTop }, 'fast', () => { this.br.animating = false });
339
+ .animate({ scrollTop: leafTop }, 'fast', () => { this.br.animating = false; });
334
340
  }
335
341
  }
336
342
  }