@internetarchive/bookreader 5.0.0-6-14 → 5.0.0-60

Sign up to get free protection for your applications and to get access to all the features.
Files changed (271) hide show
  1. package/.eslintrc.js +17 -15
  2. package/.github/workflows/node.js.yml +72 -10
  3. package/.github/workflows/npm-publish.yml +6 -20
  4. package/.testcaferc.js +10 -0
  5. package/BookReader/BookReader.css +241 -140
  6. package/BookReader/BookReader.js +1 -1
  7. package/BookReader/BookReader.js.LICENSE.txt +24 -20
  8. package/BookReader/BookReader.js.map +1 -1
  9. package/BookReader/ia-bookreader-bundle.js +1519 -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/BookReaderJSAutoplay.js +4 -1
  61. package/BookReaderDemo/BookReaderJSSimple.js +1 -0
  62. package/BookReaderDemo/IADemoBr.js +147 -0
  63. package/BookReaderDemo/demo-advanced.html +2 -2
  64. package/BookReaderDemo/demo-autoplay.html +2 -1
  65. package/BookReaderDemo/demo-embed-iframe-src.html +2 -1
  66. package/BookReaderDemo/demo-fullscreen-mobile.html +2 -1
  67. package/BookReaderDemo/demo-fullscreen.html +2 -1
  68. package/BookReaderDemo/demo-iiif.html +2 -1
  69. package/BookReaderDemo/demo-internetarchive.html +84 -17
  70. package/BookReaderDemo/demo-multiple.html +2 -1
  71. package/BookReaderDemo/demo-preview-pages.html +2 -1
  72. package/BookReaderDemo/demo-simple.html +2 -1
  73. package/BookReaderDemo/demo-vendor-fullscreen.html +2 -1
  74. package/BookReaderDemo/ia-multiple-volumes-manifest.js +170 -0
  75. package/BookReaderDemo/immersion-1up.html +2 -1
  76. package/BookReaderDemo/immersion-mode.html +2 -1
  77. package/BookReaderDemo/toggle_controls.html +2 -1
  78. package/BookReaderDemo/view_mode.html +2 -1
  79. package/BookReaderDemo/viewmode-cycle.html +2 -3
  80. package/CHANGELOG.md +244 -0
  81. package/README.md +14 -1
  82. package/babel.config.js +19 -0
  83. package/codecov.yml +6 -0
  84. package/index.html +3 -0
  85. package/jsconfig.json +19 -0
  86. package/netlify.toml +5 -0
  87. package/package.json +70 -59
  88. package/renovate.json +52 -0
  89. package/scripts/preversion.js +4 -1
  90. package/src/BookNavigator/assets/bookmark-colors.js +1 -1
  91. package/src/BookNavigator/assets/button-base.js +9 -2
  92. package/src/BookNavigator/assets/ia-logo.js +17 -0
  93. package/src/BookNavigator/assets/icon_checkmark.js +1 -1
  94. package/src/BookNavigator/assets/icon_close.js +1 -1
  95. package/src/BookNavigator/assets/icon_sort_asc.js +5 -0
  96. package/src/BookNavigator/assets/icon_sort_desc.js +5 -0
  97. package/src/BookNavigator/assets/icon_sort_neutral.js +5 -0
  98. package/src/BookNavigator/assets/icon_volumes.js +11 -0
  99. package/src/BookNavigator/book-navigator.js +585 -0
  100. package/src/BookNavigator/bookmarks/bookmark-button.js +3 -2
  101. package/src/BookNavigator/bookmarks/bookmark-edit.js +3 -4
  102. package/src/BookNavigator/bookmarks/bookmarks-list.js +2 -3
  103. package/src/BookNavigator/bookmarks/bookmarks-loginCTA.js +3 -8
  104. package/src/BookNavigator/bookmarks/bookmarks-provider.js +27 -17
  105. package/src/BookNavigator/bookmarks/ia-bookmarks.js +116 -67
  106. package/src/BookNavigator/delete-modal-actions.js +1 -1
  107. package/src/BookNavigator/downloads/downloads-provider.js +36 -21
  108. package/src/BookNavigator/downloads/downloads.js +41 -25
  109. package/src/BookNavigator/search/search-provider.js +80 -28
  110. package/src/BookNavigator/search/search-results.js +34 -25
  111. package/src/BookNavigator/sharing.js +27 -0
  112. package/src/BookNavigator/visual-adjustments/visual-adjustments-provider.js +11 -10
  113. package/src/BookNavigator/visual-adjustments/visual-adjustments.js +3 -3
  114. package/src/BookNavigator/volumes/volumes-provider.js +111 -0
  115. package/src/BookNavigator/volumes/volumes.js +188 -0
  116. package/src/BookReader/BookModel.js +59 -30
  117. package/src/BookReader/DebugConsole.js +3 -3
  118. package/src/BookReader/DragScrollable.js +233 -0
  119. package/src/BookReader/Mode1Up.js +56 -351
  120. package/src/BookReader/Mode1UpLit.js +391 -0
  121. package/src/BookReader/Mode2Up.js +73 -1318
  122. package/src/BookReader/Mode2UpLit.js +781 -0
  123. package/src/BookReader/ModeCoordinateSpace.js +29 -0
  124. package/src/BookReader/ModeSmoothZoom.js +211 -0
  125. package/src/BookReader/ModeThumb.js +17 -11
  126. package/src/BookReader/Navbar/Navbar.js +10 -36
  127. package/src/BookReader/PageContainer.js +69 -6
  128. package/src/BookReader/ReduceSet.js +1 -1
  129. package/src/BookReader/Toolbar/Toolbar.js +10 -37
  130. package/src/BookReader/events.js +2 -0
  131. package/src/BookReader/options.js +24 -2
  132. package/src/BookReader/utils/HTMLDimensionsCacher.js +44 -0
  133. package/src/BookReader/utils/ScrollClassAdder.js +31 -0
  134. package/src/BookReader/utils.js +108 -13
  135. package/src/BookReader.js +481 -828
  136. package/src/assets/icons/close-circle-dark.svg +1 -0
  137. package/src/assets/icons/magnify-minus.svg +3 -7
  138. package/src/assets/icons/magnify-plus.svg +3 -7
  139. package/src/assets/icons/voice.svg +1 -0
  140. package/src/css/_BRBookmarks.scss +1 -1
  141. package/src/css/_BRComponent.scss +1 -1
  142. package/src/css/_BRmain.scss +33 -0
  143. package/src/css/_BRnav.scss +4 -26
  144. package/src/css/_BRpages.scss +147 -40
  145. package/src/css/_BRsearch.scss +25 -11
  146. package/src/css/_TextSelection.scss +16 -17
  147. package/src/css/_colorbox.scss +2 -2
  148. package/src/css/_controls.scss +17 -5
  149. package/src/css/_icons.scss +7 -1
  150. package/src/ia-bookreader/ia-bookreader.js +224 -0
  151. package/src/plugins/plugin.archive_analytics.js +3 -3
  152. package/src/plugins/plugin.autoplay.js +4 -9
  153. package/src/plugins/plugin.chapters.js +28 -35
  154. package/src/plugins/plugin.mobile_nav.js +11 -10
  155. package/src/plugins/plugin.resume.js +3 -3
  156. package/src/plugins/plugin.text_selection.js +32 -41
  157. package/src/plugins/plugin.vendor-fullscreen.js +4 -4
  158. package/src/plugins/search/plugin.search.js +179 -103
  159. package/src/plugins/search/view.js +59 -44
  160. package/src/plugins/tts/AbstractTTSEngine.js +46 -37
  161. package/src/plugins/tts/FestivalTTSEngine.js +13 -14
  162. package/src/plugins/tts/PageChunk.js +15 -21
  163. package/src/plugins/tts/PageChunkIterator.js +8 -12
  164. package/src/plugins/tts/WebTTSEngine.js +87 -71
  165. package/src/plugins/tts/plugin.tts.js +94 -125
  166. package/src/plugins/tts/utils.js +0 -25
  167. package/src/plugins/url/UrlPlugin.js +193 -0
  168. package/src/plugins/{plugin.url.js → url/plugin.url.js} +45 -16
  169. package/src/util/docCookies.js +21 -2
  170. package/tests/e2e/README.md +37 -0
  171. package/tests/e2e/autoplay.test.js +2 -2
  172. package/tests/e2e/base.test.js +7 -7
  173. package/tests/e2e/helpers/base.js +28 -23
  174. package/tests/e2e/helpers/debug.js +1 -1
  175. package/tests/e2e/helpers/desktopSearch.js +14 -13
  176. package/tests/e2e/helpers/mobileSearch.js +3 -3
  177. package/tests/e2e/helpers/params.js +17 -0
  178. package/tests/e2e/helpers/rightToLeft.js +4 -10
  179. package/tests/e2e/models/Navigation.js +13 -4
  180. package/tests/e2e/rightToLeft.test.js +4 -5
  181. package/tests/e2e/viewmode.test.js +40 -33
  182. package/tests/jest/BookNavigator/book-navigator.test.js +658 -0
  183. package/tests/jest/BookNavigator/bookmarks/bookmark-button.test.js +43 -0
  184. package/tests/{karma → jest}/BookNavigator/bookmarks/bookmark-edit.test.js +25 -26
  185. package/tests/{karma → jest}/BookNavigator/bookmarks/bookmarks-list.test.js +41 -42
  186. package/tests/jest/BookNavigator/bookmarks/ia-bookmarks.test.js +45 -0
  187. package/tests/jest/BookNavigator/downloads/downloads-provider.test.js +67 -0
  188. package/tests/jest/BookNavigator/downloads/downloads.test.js +53 -0
  189. package/tests/jest/BookNavigator/search/search-provider.test.js +167 -0
  190. package/tests/{karma/BookNavigator → jest/BookNavigator/search}/search-results.test.js +104 -60
  191. package/tests/jest/BookNavigator/sharing/sharing-provider.test.js +49 -0
  192. package/tests/jest/BookNavigator/visual-adjustments.test.js +200 -0
  193. package/tests/jest/BookNavigator/volumes/volumes-provider.test.js +184 -0
  194. package/tests/jest/BookNavigator/volumes/volumes.test.js +97 -0
  195. package/tests/{BookReader → jest/BookReader}/BookModel.test.js +59 -14
  196. package/tests/jest/BookReader/BookReaderPublicFunctions.test.js +193 -0
  197. package/tests/{BookReader → jest/BookReader}/DebugConsole.test.js +1 -1
  198. package/tests/{BookReader → jest/BookReader}/ImageCache.test.js +4 -4
  199. package/tests/jest/BookReader/Mode1UpLit.test.js +73 -0
  200. package/tests/jest/BookReader/Mode2Up.test.js +98 -0
  201. package/tests/jest/BookReader/Mode2UpLit.test.js +190 -0
  202. package/tests/jest/BookReader/ModeCoordinateSpace.test.js +16 -0
  203. package/tests/jest/BookReader/ModeSmoothZoom.test.js +175 -0
  204. package/tests/jest/BookReader/ModeThumb.test.js +71 -0
  205. package/tests/{BookReader → jest/BookReader}/Navbar/Navbar.test.js +10 -10
  206. package/tests/{BookReader → jest/BookReader}/PageContainer.test.js +88 -6
  207. package/tests/{BookReader → jest/BookReader}/ReduceSet.test.js +1 -1
  208. package/tests/{BookReader → jest/BookReader}/Toolbar/Toolbar.test.js +2 -2
  209. package/tests/jest/BookReader/utils/HTMLDimensionsCacher.test.js +59 -0
  210. package/tests/jest/BookReader/utils/ScrollClassAdder.test.js +49 -0
  211. package/tests/{BookReader → jest/BookReader}/utils/classes.test.js +1 -1
  212. package/tests/jest/BookReader/utils.test.js +217 -0
  213. package/tests/jest/BookReader.keyboard.test.js +190 -0
  214. package/tests/{BookReader.options.test.js → jest/BookReader.options.test.js} +9 -1
  215. package/tests/{BookReader.test.js → jest/BookReader.test.js} +26 -37
  216. package/tests/{plugins → jest/plugins}/plugin.archive_analytics.test.js +2 -2
  217. package/tests/{plugins → jest/plugins}/plugin.autoplay.test.js +4 -4
  218. package/tests/{plugins → jest/plugins}/plugin.chapters.test.js +10 -11
  219. package/tests/{plugins → jest/plugins}/plugin.iframe.test.js +2 -2
  220. package/tests/{plugins → jest/plugins}/plugin.mobile_nav.test.js +5 -5
  221. package/tests/{plugins → jest/plugins}/plugin.resume.test.js +3 -3
  222. package/tests/{plugins → jest/plugins}/plugin.text_selection.test.js +39 -47
  223. package/tests/{plugins → jest/plugins}/plugin.vendor-fullscreen.test.js +2 -2
  224. package/tests/{plugins → jest/plugins}/search/plugin.search.test.js +57 -47
  225. package/tests/{plugins → jest/plugins}/search/plugin.search.view.test.js +35 -6
  226. package/tests/{plugins → jest/plugins}/tts/AbstractTTSEngine.test.js +9 -9
  227. package/tests/{plugins → jest/plugins}/tts/FestivalTTSEngine.test.js +4 -4
  228. package/tests/{plugins → jest/plugins}/tts/PageChunk.test.js +1 -1
  229. package/tests/{plugins → jest/plugins}/tts/PageChunkIterator.test.js +3 -3
  230. package/tests/{plugins → jest/plugins}/tts/WebTTSEngine.test.js +47 -1
  231. package/tests/{plugins → jest/plugins}/tts/utils.test.js +1 -60
  232. package/tests/jest/plugins/url/UrlPlugin.test.js +190 -0
  233. package/tests/{plugins → jest/plugins/url}/plugin.url.test.js +53 -14
  234. package/tests/jest/setup.js +3 -0
  235. package/tests/{util → jest/util}/browserSniffing.test.js +1 -1
  236. package/tests/jest/util/docCookies.test.js +24 -0
  237. package/tests/{util → jest/util}/strings.test.js +1 -1
  238. package/tests/{utils.js → jest/utils.js} +38 -0
  239. package/webpack.config.js +11 -5
  240. package/.babelrc +0 -12
  241. package/.dependabot/config.yml +0 -6
  242. package/.testcaferc.json +0 -5
  243. package/BookReader/bookreader-component-bundle.js +0 -1450
  244. package/BookReader/bookreader-component-bundle.js.LICENSE.txt +0 -38
  245. package/BookReader/bookreader-component-bundle.js.map +0 -1
  246. package/BookReader/jquery-1.10.1.js +0 -2
  247. package/BookReader/jquery-1.10.1.js.LICENSE.txt +0 -24
  248. package/BookReader/plugins/plugin.menu_toggle.js +0 -2
  249. package/BookReader/plugins/plugin.menu_toggle.js.map +0 -1
  250. package/BookReaderDemo/bookreader-template-bundle.js +0 -7178
  251. package/BookReaderDemo/demo-plugin-menu-toggle.html +0 -34
  252. package/karma.conf.js +0 -23
  253. package/src/BookNavigator/BookModel.js +0 -14
  254. package/src/BookNavigator/BookNavigator.js +0 -438
  255. package/src/BookNavigator/assets/book-loader.js +0 -27
  256. package/src/BookNavigator/br-fullscreen-mgr.js +0 -83
  257. package/src/BookNavigator/search/a-search-result.js +0 -55
  258. package/src/BookReaderComponent/BookReaderComponent.js +0 -112
  259. package/src/ItemNavigator/ItemNavigator.js +0 -372
  260. package/src/ItemNavigator/providers/sharing.js +0 -29
  261. package/src/dragscrollable-br.js +0 -261
  262. package/src/plugins/menu_toggle/plugin.menu_toggle.js +0 -324
  263. package/tests/BookReader/BookReaderPublicFunctions.test.js +0 -171
  264. package/tests/BookReader/Mode1Up.test.js +0 -164
  265. package/tests/BookReader/Mode2Up.test.js +0 -247
  266. package/tests/BookReader/utils.test.js +0 -109
  267. package/tests/e2e/ia-production/ia-prod-base.js +0 -17
  268. package/tests/karma/BookNavigator/book-navigator.test.js +0 -132
  269. package/tests/karma/BookNavigator/visual-adjustments.test.js +0 -201
  270. package/tests/plugins/menu_toggle/plugin.menu_toggle.test.js +0 -68
  271. package/tests/util/docCookies.test.js +0 -15
@@ -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
+ }
@@ -0,0 +1,211 @@
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 {import("./options.js").AutoFitValues} autoFit
10
+ * @property {number} scale
11
+ * @property {{ x: number, y: number }} scaleCenter
12
+ * @property {HTMLDimensionsCacher} htmlDimensionsCacher
13
+ * @property {function(): void} [attachScrollListeners]
14
+ * @property {function(): void} [detachScrollListeners]
15
+ */
16
+
17
+ /** Manages pinch-zoom, ctrl-wheel, and trackpad pinch smooth zooming. */
18
+ export class ModeSmoothZoom {
19
+ /** @param {SmoothZoomable} mode */
20
+ constructor(mode) {
21
+ /** @type {SmoothZoomable} */
22
+ this.mode = mode;
23
+
24
+ /** Non-null when a scale has been enqueued/is being processed by the buffer function */
25
+ this.pinchMoveFrame = null;
26
+ /** Promise for the current/enqueued pinch move frame. Resolves when it is complete. */
27
+ this.pinchMoveFramePromise = Promise.resolve();
28
+ this.oldScale = 1;
29
+ /** @type {{ scale: number, center: { x: number, y: number }}} */
30
+ this.lastEvent = null;
31
+ this.attached = false;
32
+
33
+ /** @type {function(function(): void): any} */
34
+ this.bufferFn = window.requestAnimationFrame.bind(window);
35
+
36
+ // Hammer.js by default set userSelect to None; we don't want that!
37
+ // TODO: Is there any way to do this not globally on Hammer?
38
+ delete Hammer.defaults.cssProps.userSelect;
39
+ this.hammer = new Hammer.Manager(this.mode.$container, {
40
+ touchAction: "pan-x pan-y",
41
+ });
42
+
43
+ this.hammer.add(new Hammer.Pinch());
44
+ }
45
+
46
+ attach() {
47
+ if (this.attached) return;
48
+
49
+ this.attachCtrlZoom();
50
+
51
+ // GestureEvents work only on Safari; they interfere with Hammer,
52
+ // so block them.
53
+ this.mode.$container.addEventListener('gesturestart', this._preventEvent);
54
+ this.mode.$container.addEventListener('gesturechange', this._preventEvent);
55
+ this.mode.$container.addEventListener('gestureend', this._preventEvent);
56
+
57
+ // The pinch listeners
58
+ this.hammer.on("pinchstart", this._pinchStart);
59
+ this.hammer.on("pinchmove", this._pinchMove);
60
+ this.hammer.on("pinchend", this._pinchEnd);
61
+ this.hammer.on("pinchcancel", this._pinchCancel);
62
+
63
+ this.attached = true;
64
+ }
65
+
66
+ detach() {
67
+ this.detachCtrlZoom();
68
+
69
+ // GestureEvents work only on Safari; they interfere with Hammer,
70
+ // so block them.
71
+ this.mode.$container.removeEventListener('gesturestart', this._preventEvent);
72
+ this.mode.$container.removeEventListener('gesturechange', this._preventEvent);
73
+ this.mode.$container.removeEventListener('gestureend', this._preventEvent);
74
+
75
+ // The pinch listeners
76
+ this.hammer.off("pinchstart", this._pinchStart);
77
+ this.hammer.off("pinchmove", this._pinchMove);
78
+ this.hammer.off("pinchend", this._pinchEnd);
79
+ this.hammer.off("pinchcancel", this._pinchCancel);
80
+
81
+ this.attached = false;
82
+ }
83
+
84
+ /** @param {Event} ev */
85
+ _preventEvent = (ev) => {
86
+ ev.preventDefault();
87
+ return false;
88
+ }
89
+
90
+ _pinchStart = () => {
91
+ // Do this in case the pinchend hasn't fired yet.
92
+ this.oldScale = 1;
93
+ this.mode.$visibleWorld.classList.add("BRsmooth-zooming");
94
+ this.mode.$visibleWorld.style.willChange = "transform";
95
+ this.mode.autoFit = "none";
96
+ this.detachCtrlZoom();
97
+ this.mode.detachScrollListeners?.();
98
+ }
99
+
100
+ /** @param {{ scale: number, center: { x: number, y: number }}} e */
101
+ _pinchMove = async (e) => {
102
+ this.lastEvent = e;
103
+ if (!this.pinchMoveFrame) {
104
+ let pinchMoveFramePromiseRes = null;
105
+ this.pinchMoveFramePromise = new Promise(
106
+ (res) => (pinchMoveFramePromiseRes = res)
107
+ );
108
+
109
+ // Buffer these events; only update the scale when request animation fires
110
+ this.pinchMoveFrame = this.bufferFn(() => {
111
+ this.updateScaleCenter({
112
+ clientX: this.lastEvent.center.x,
113
+ clientY: this.lastEvent.center.y,
114
+ });
115
+ this.mode.scale *= this.lastEvent.scale / this.oldScale;
116
+ this.oldScale = this.lastEvent.scale;
117
+ this.pinchMoveFrame = null;
118
+ pinchMoveFramePromiseRes();
119
+ });
120
+ }
121
+ }
122
+
123
+ _pinchEnd = async () => {
124
+ // Want this to happen after the pinchMoveFrame,
125
+ // if one is in progress; otherwise setting oldScale
126
+ // messes up the transform.
127
+ await this.pinchMoveFramePromise;
128
+ this.mode.scaleCenter = { x: 0.5, y: 0.5 };
129
+ this.oldScale = 1;
130
+ this.mode.$visibleWorld.classList.remove("BRsmooth-zooming");
131
+ this.mode.$visibleWorld.style.willChange = "auto";
132
+ this.attachCtrlZoom();
133
+ this.mode.attachScrollListeners?.();
134
+ }
135
+
136
+ _pinchCancel = async () => {
137
+ // iOS fires pinchcancel ~randomly; it looks like it sometimes
138
+ // thinks the pinch becomes a pan, at which point it cancels?
139
+ await this._pinchEnd();
140
+ }
141
+
142
+ /** @private */
143
+ attachCtrlZoom() {
144
+ window.addEventListener("wheel", this._handleCtrlWheel, { passive: false });
145
+ }
146
+
147
+ /** @private */
148
+ detachCtrlZoom() {
149
+ window.removeEventListener("wheel", this._handleCtrlWheel);
150
+ }
151
+
152
+ /** @param {WheelEvent} ev **/
153
+ _handleCtrlWheel = (ev) => {
154
+ if (!ev.ctrlKey) return;
155
+ ev.preventDefault();
156
+ const zoomMultiplier =
157
+ // Zooming on macs was painfully slow; likely due to their better
158
+ // trackpads. Give them a higher zoom rate.
159
+ /Mac/i.test(navigator.platform)
160
+ ? 0.045
161
+ : // This worked well for me on Windows
162
+ 0.03;
163
+
164
+ // Zoom around the cursor
165
+ this.updateScaleCenter(ev);
166
+ this.mode.autoFit = "none";
167
+ this.mode.scale *= 1 - Math.sign(ev.deltaY) * zoomMultiplier;
168
+ }
169
+
170
+ /**
171
+ * @param {object} param0
172
+ * @param {number} param0.clientX
173
+ * @param {number} param0.clientY
174
+ */
175
+ updateScaleCenter({ clientX, clientY }) {
176
+ const bc = this.mode.htmlDimensionsCacher.boundingClientRect;
177
+ this.mode.scaleCenter = {
178
+ x: (clientX - bc.left) / this.mode.htmlDimensionsCacher.clientWidth,
179
+ y: (clientY - bc.top) / this.mode.htmlDimensionsCacher.clientHeight,
180
+ };
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
+ }
211
+ }
@@ -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
  }
@@ -4,6 +4,7 @@ import 'jquery-ui/ui/widget.js';
4
4
  import 'jquery-ui/ui/widgets/mouse.js';
5
5
  import 'jquery-ui/ui/widgets/slider.js';
6
6
  import { EVENTS } from '../events.js';
7
+ import { throttle } from '../utils.js';
7
8
 
8
9
  export class Navbar {
9
10
  /**
@@ -27,6 +28,8 @@ export class Navbar {
27
28
  this.maximumControls = [
28
29
  'book_left', 'book_right', 'zoom_in', 'zoom_out', 'onepg', 'twopg', 'thumb'
29
30
  ];
31
+
32
+ this.updateNavIndexThrottled = throttle(this.updateNavIndex.bind(this), 250, false);
30
33
  }
31
34
 
32
35
  controlFor(controlName) {
@@ -213,7 +216,7 @@ export class Navbar {
213
216
  const $slider = this.$root.find('.BRpager').slider({
214
217
  animate: true,
215
218
  min: 0,
216
- max: br.getNumLeafs() - 1,
219
+ max: br.book.getNumLeafs() - 1,
217
220
  value: br.currentIndex(),
218
221
  range: "min"
219
222
  });
@@ -240,35 +243,6 @@ export class Navbar {
240
243
  return this.$nav;
241
244
  }
242
245
 
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
246
  /**
273
247
  * Returns the textual representation of the current page for the navbar
274
248
  * @param {number} index
@@ -277,16 +251,16 @@ export class Navbar {
277
251
  getNavPageNumString(index) {
278
252
  const { br } = this;
279
253
  // Accessible index starts at 0 (alas) so we add 1 to make human
280
- const pageNum = br.getPageNum(index);
281
- const pageType = br.getPageProp(index, 'pageType');
282
- const numLeafs = br.getNumLeafs();
254
+ const pageNum = br.book.getPageNum(index);
255
+ const pageType = br.book.getPageProp(index, 'pageType');
256
+ const numLeafs = br.book.getNumLeafs();
283
257
 
284
258
  if (!this.maxPageNum) {
285
259
  // Calculate Max page num (used for pagination display)
286
260
  let maxPageNum = 0;
287
261
  let pageNumVal;
288
262
  for (let i = 0; i < numLeafs; i++) {
289
- pageNumVal = br.getPageNum(i);
263
+ pageNumVal = br.book.getPageNum(i);
290
264
  if (!isNaN(pageNumVal) && pageNumVal > maxPageNum) {
291
265
  maxPageNum = pageNumVal;
292
266
  }
@@ -331,9 +305,9 @@ export function getNavPageNumHtml(index, numLeafs, pageNum, pageType, maxPageNum
331
305
 
332
306
  if (!pageIsAsserted) {
333
307
  const pageIndex = index + 1;
334
- return `Page (${pageIndex} of ${numLeafs})`; // Page (8 of 10)
308
+ return `(${pageIndex} of ${numLeafs})`; // Page (8 of 10)
335
309
  }
336
310
 
337
311
  const bookLengthLabel = maxPageNum ? ` of ${maxPageNum}` : '';
338
- return `Page ${pageNum}${bookLengthLabel}`;
312
+ return `${pageNum}${bookLengthLabel}`;
339
313
  }
@@ -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,60 @@ 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
+
107
+ // Some style; corner radius 4px. Can't set this in CSS yet
108
+ rect.setAttribute("rx", "4");
109
+ rect.setAttribute("ry", "4");
110
+ return rect;
111
+ }
112
+
113
+ /**
114
+ * @param {string} layerClass
115
+ * @param {Array<{ l: number, r: number, b: number, t: number }>} boxes
116
+ * @param {PageModel} page
117
+ * @param {HTMLElement} containerEl
118
+ * @param {string[]} [rectClasses] CSS classes to add to the rects
119
+ */
120
+ export function renderBoxesInPageContainerLayer(layerClass, boxes, page, containerEl, rectClasses = null) {
121
+ const mountedSvg = containerEl.querySelector(`.${layerClass}`);
122
+ // Create the layer if it's not there
123
+ const svg = mountedSvg || createSVGPageLayer(page, layerClass);
124
+ if (!mountedSvg) {
125
+ // Insert after the image if the image is already loaded.
126
+ const imgEl = containerEl.querySelector('.BRpageimage');
127
+ if (imgEl) $(svg).insertAfter(imgEl);
128
+ else $(svg).prependTo(containerEl);
129
+ }
130
+
131
+ for (const [i, box] of boxes.entries()) {
132
+ const rect = boxToSVGRect(box);
133
+ if (rectClasses) {
134
+ rect.setAttribute('class', rectClasses[i]);
135
+ }
136
+ svg.appendChild(rect);
137
+ }
138
+ }
@@ -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,
@@ -54,7 +54,7 @@ export class Toolbar {
54
54
  .attr({href: br.bookUrl, title: br.bookUrlTitle})
55
55
  .addClass('BRreturn')
56
56
  .html(br.bookUrlText || br.bookTitle)
57
- )
57
+ );
58
58
  } else if (br.bookTitle) {
59
59
  $titleSectionEl.append(br.bookUrlText || br.bookTitle);
60
60
  }
@@ -75,8 +75,6 @@ export class Toolbar {
75
75
  br.$('.BRnavCntl').addClass('BRup');
76
76
  br.$('.pause').hide();
77
77
 
78
- this.updateToolbarZoom(br.reduce); // Pretty format
79
-
80
78
  // We build in mode 2
81
79
  br.refs.$BRtoolbar.append();
82
80
 
@@ -127,31 +125,6 @@ export class Toolbar {
127
125
  });
128
126
  }
129
127
 
130
- /**
131
- * @deprecated
132
- * @todo .BRzoom doesn't exist anywhere, so this is likely dead code
133
- * Update the displayed zoom factor based on reduction factor
134
- * @param {number} reduce
135
- */
136
- updateToolbarZoom(reduce) {
137
- const { br } = this;
138
- // $$$ TODO preserve zoom/fit for each mode
139
- const autofit = br.mode == br.constMode2up ? br.twoPage.autofit : br.onePage.autofit;
140
- /** @type {string} */
141
- let value;
142
- if (autofit) {
143
- value = autofit.slice(0,1).toUpperCase() + autofit.slice(1);
144
- } else {
145
- value = (100 / reduce)
146
- .toFixed(2)
147
- // Strip trailing zeroes and decimal if all zeroes
148
- .replace(/0+$/,'')
149
- .replace(/\.$/,'')
150
- + '%';
151
- }
152
- br.$('.BRzoom').text(value);
153
- }
154
-
155
128
  /**
156
129
  * @param {JQuery} $shareDiv
157
130
  */
@@ -212,11 +185,11 @@ export class Toolbar {
212
185
  $form.appendTo($shareDiv);
213
186
 
214
187
  $form.find('.fieldset-embed input').on('change', event => {
215
- const form = $(event.target).parents('form:first');
188
+ const form = $(event.target).parents('form').first();
216
189
  const params = {};
217
190
  params.mode = $(form.find('.fieldset-embed input[name=pages]:checked')).val();
218
191
  if (form.find('.fieldset-embed input[name=thispage]').prop('checked')) {
219
- params.page = br.getPageNum(br.currentIndex());
192
+ params.page = br.book.getPageNum(br.currentIndex());
220
193
  }
221
194
 
222
195
  if (br.getEmbedCode) {
@@ -232,12 +205,12 @@ export class Toolbar {
232
205
  // Bind share buttons
233
206
 
234
207
  // Use url without hashes
235
- $form.find('.facebook-share-button').click(() => {
208
+ $form.find('.facebook-share-button').on("click", () => {
236
209
  const params = $.param({ u: this._getSocialShareUrl() });
237
210
  const url = 'https://www.facebook.com/sharer.php?' + params;
238
211
  createPopup(url, 600, 400, 'Share');
239
212
  });
240
- $form.find('.twitter-share-button').click(() => {
213
+ $form.find('.twitter-share-button').on("click", () => {
241
214
  const params = $.param({
242
215
  url: this._getSocialShareUrl(),
243
216
  text: br.bookTitle
@@ -245,7 +218,7 @@ export class Toolbar {
245
218
  const url = 'https://twitter.com/intent/tweet?' + params;
246
219
  createPopup(url, 600, 400, 'Share');
247
220
  });
248
- $form.find('.email-share-button').click(() => {
221
+ $form.find('.email-share-button').on("click", () => {
249
222
  const body = `${br.bookTitle}\n\n${this._getSocialShareUrl()}`;
250
223
  window.location.href = `mailto:?subject=${encodeURI(br.bookTitle)}&body=${encodeURI(body)}`;
251
224
  });
@@ -331,11 +304,11 @@ export class Toolbar {
331
304
  }
332
305
  }
333
306
 
334
- export function blankInfoDiv() {
307
+ function blankInfoDiv() {
335
308
  return $(`
336
309
  <div class="BRfloat BRinfo">
337
310
  <div class="BRfloatHead">About this book
338
- <button class="floatShut" href="javascript:;" onclick="$.fn.colorbox.close();"><span class="shift">Close</span></button>
311
+ <button class="floatShut" href="javascript:;" onclick="$.fn.colorbox.close();"><span class="br-colorbox-shift">Close</span></button>
339
312
  </div>
340
313
  <div class="BRfloatBody">
341
314
  <div class="BRfloatCover"></div>
@@ -351,12 +324,12 @@ export function blankInfoDiv() {
351
324
  </div>`);
352
325
  }
353
326
 
354
- export function blankShareDiv() {
327
+ function blankShareDiv() {
355
328
  return $(`
356
329
  <div class="BRfloat BRshare">
357
330
  <div class="BRfloatHead">
358
331
  Share
359
- <button class="floatShut" href="javascript:;" onclick="$.fn.colorbox.close();"><span class="shift">Close</span></button>
332
+ <button class="floatShut" href="javascript:;" onclick="$.fn.colorbox.close();"><span class="br-colorbox-shift">Close</span></button>
360
333
  </div>
361
334
  </div>`);
362
335
  }
@@ -3,9 +3,11 @@ export const EVENTS = {
3
3
  /** Indicates that the fragment (a serialization of the reader
4
4
  * state) has changed. */
5
5
  fragmentChange: 'fragmentChange',
6
+ pageChanged: 'pageChanged',
6
7
  PostInit: 'PostInit',
7
8
  stop: 'stop',
8
9
  resize: 'resize',
10
+ userAction: 'userAction', // event to know if user is actively reading
9
11
  // nav events:
10
12
  navToggled: 'navToggled',
11
13
  // menu click events