@internetarchive/bookreader 5.0.0-28-remove-url-defaults → 5.0.0-29

Sign up to get free protection for your applications and to get access to all the features.
Files changed (226) hide show
  1. package/.husky/_/husky.sh +30 -0
  2. package/BookReader/BookReader.css +1 -1
  3. package/BookReader/BookReader.js +1 -1
  4. package/BookReader/BookReader.js.map +1 -1
  5. package/BookReader/bookreader-component-bundle.js +570 -542
  6. package/BookReader/bookreader-component-bundle.js.LICENSE.txt +23 -0
  7. package/BookReader/bookreader-component-bundle.js.map +1 -1
  8. package/BookReader/plugins/plugin.search.js +1 -1
  9. package/BookReader/plugins/plugin.search.js.map +1 -1
  10. package/BookReaderDemo/BookReaderDemo.css +14 -1
  11. package/BookReaderDemo/IADemoBr.js +104 -0
  12. package/BookReaderDemo/demo-internetarchive.html +65 -98
  13. package/CHANGELOG.md +4 -0
  14. package/package.json +9 -6
  15. package/src/BookNavigator/assets/ia-logo.js +17 -0
  16. package/src/BookNavigator/book-navigator.js +521 -0
  17. package/src/BookNavigator/bookmarks/bookmark-button.js +2 -1
  18. package/src/BookNavigator/bookmarks/bookmarks-provider.js +20 -8
  19. package/src/BookNavigator/bookmarks/ia-bookmarks.js +84 -51
  20. package/src/BookNavigator/downloads/downloads-provider.js +5 -9
  21. package/src/BookNavigator/downloads/downloads.js +1 -0
  22. package/src/BookNavigator/search/search-provider.js +15 -8
  23. package/src/BookNavigator/sharing.js +27 -0
  24. package/src/BookNavigator/visual-adjustments/visual-adjustments-provider.js +9 -8
  25. package/src/BookNavigator/volumes/volumes-provider.js +3 -4
  26. package/src/BookReader/options.js +6 -0
  27. package/src/BookReader.js +20 -8
  28. package/src/BookReaderComponent/BookReaderComponent.js +53 -32
  29. package/src/css/_BRComponent.scss +1 -1
  30. package/src/plugins/search/plugin.search.js +9 -9
  31. package/{src → stat}/BookNavigator/BookModel.js +0 -0
  32. package/{src → stat}/BookNavigator/BookNavigator.js +109 -102
  33. package/stat/BookNavigator/assets/bookmark-colors.js +15 -0
  34. package/stat/BookNavigator/assets/button-base.js +61 -0
  35. package/stat/BookNavigator/assets/ia-logo.js +17 -0
  36. package/stat/BookNavigator/assets/icon_checkmark.js +6 -0
  37. package/stat/BookNavigator/assets/icon_close.js +3 -0
  38. package/stat/BookNavigator/assets/icon_sort_asc.js +5 -0
  39. package/stat/BookNavigator/assets/icon_sort_desc.js +5 -0
  40. package/stat/BookNavigator/assets/icon_sort_neutral.js +5 -0
  41. package/stat/BookNavigator/assets/icon_volumes.js +11 -0
  42. package/stat/BookNavigator/bookmarks/bookmark-button.js +64 -0
  43. package/stat/BookNavigator/bookmarks/bookmark-edit.js +215 -0
  44. package/stat/BookNavigator/bookmarks/bookmarks-list.js +285 -0
  45. package/stat/BookNavigator/bookmarks/bookmarks-loginCTA.js +28 -0
  46. package/stat/BookNavigator/bookmarks/bookmarks-provider.js +56 -0
  47. package/stat/BookNavigator/bookmarks/ia-bookmarks.js +523 -0
  48. package/{src → stat}/BookNavigator/br-fullscreen-mgr.js +1 -2
  49. package/stat/BookNavigator/delete-modal-actions.js +49 -0
  50. package/stat/BookNavigator/downloads/downloads-provider.js +72 -0
  51. package/stat/BookNavigator/downloads/downloads.js +139 -0
  52. package/stat/BookNavigator/provider-config.js +0 -0
  53. package/stat/BookNavigator/search/a-search-result.js +55 -0
  54. package/stat/BookNavigator/search/search-provider.js +180 -0
  55. package/stat/BookNavigator/search/search-results.js +360 -0
  56. package/{src/ItemNavigator/providers → stat/BookNavigator}/sharing.js +3 -5
  57. package/stat/BookNavigator/visual-adjustments/visual-adjustments-provider.js +94 -0
  58. package/stat/BookNavigator/visual-adjustments/visual-adjustments.js +280 -0
  59. package/stat/BookNavigator/volumes/volumes-provider.js +83 -0
  60. package/stat/BookNavigator/volumes/volumes.js +178 -0
  61. package/stat/BookReader/BookModel.js +518 -0
  62. package/stat/BookReader/DebugConsole.js +54 -0
  63. package/stat/BookReader/DragScrollable.js +233 -0
  64. package/stat/BookReader/ImageCache.js +116 -0
  65. package/stat/BookReader/Mode1Up.js +102 -0
  66. package/stat/BookReader/Mode1UpLit.js +434 -0
  67. package/stat/BookReader/Mode2Up.js +1372 -0
  68. package/stat/BookReader/ModeSmoothZoom.js +177 -0
  69. package/stat/BookReader/ModeThumb.js +344 -0
  70. package/stat/BookReader/Navbar/Navbar.js +310 -0
  71. package/stat/BookReader/PageContainer.js +120 -0
  72. package/stat/BookReader/ReduceSet.js +26 -0
  73. package/stat/BookReader/Toolbar/Toolbar.js +384 -0
  74. package/stat/BookReader/events.js +20 -0
  75. package/stat/BookReader/options.js +324 -0
  76. package/stat/BookReader/utils/HTMLDimensionsCacher.js +44 -0
  77. package/stat/BookReader/utils/classes.js +36 -0
  78. package/stat/BookReader/utils.js +240 -0
  79. package/stat/BookReader.js +2550 -0
  80. package/stat/BookReaderComponent/BookReaderComponent.js +117 -0
  81. package/stat/assets/icons/1up.svg +12 -0
  82. package/stat/assets/icons/2up.svg +15 -0
  83. package/stat/assets/icons/advance.svg +26 -0
  84. package/stat/assets/icons/chevron-right.svg +1 -0
  85. package/stat/assets/icons/close-circle-dark.svg +1 -0
  86. package/stat/assets/icons/close-circle.svg +1 -0
  87. package/stat/assets/icons/fullscreen.svg +17 -0
  88. package/stat/assets/icons/fullscreen_exit.svg +17 -0
  89. package/stat/assets/icons/hamburger.svg +15 -0
  90. package/stat/assets/icons/left-arrow.svg +12 -0
  91. package/stat/assets/icons/magnify-minus.svg +16 -0
  92. package/stat/assets/icons/magnify-plus.svg +17 -0
  93. package/stat/assets/icons/magnify.svg +15 -0
  94. package/stat/assets/icons/pause.svg +23 -0
  95. package/stat/assets/icons/play.svg +22 -0
  96. package/stat/assets/icons/playback-speed.svg +34 -0
  97. package/stat/assets/icons/read-aloud.svg +22 -0
  98. package/stat/assets/icons/review.svg +22 -0
  99. package/stat/assets/icons/thumbnails.svg +17 -0
  100. package/stat/assets/icons/voice.svg +1 -0
  101. package/stat/assets/icons/volume-full.svg +22 -0
  102. package/stat/assets/images/BRicons.png +0 -0
  103. package/stat/assets/images/BRicons.svg +94 -0
  104. package/stat/assets/images/BRicons_ia.png +0 -0
  105. package/stat/assets/images/back_pages.png +0 -0
  106. package/stat/assets/images/book_bottom_icon.png +0 -0
  107. package/stat/assets/images/book_down_icon.png +0 -0
  108. package/stat/assets/images/book_left_icon.png +0 -0
  109. package/stat/assets/images/book_leftmost_icon.png +0 -0
  110. package/stat/assets/images/book_right_icon.png +0 -0
  111. package/stat/assets/images/book_rightmost_icon.png +0 -0
  112. package/stat/assets/images/book_top_icon.png +0 -0
  113. package/stat/assets/images/book_up_icon.png +0 -0
  114. package/stat/assets/images/books_graphic.svg +177 -0
  115. package/stat/assets/images/booksplit.png +0 -0
  116. package/stat/assets/images/control_pause_icon.png +0 -0
  117. package/stat/assets/images/control_play_icon.png +0 -0
  118. package/stat/assets/images/embed_icon.png +0 -0
  119. package/stat/assets/images/icon-home-ia.png +0 -0
  120. package/stat/assets/images/icon_OL-logo-xs.png +0 -0
  121. package/stat/assets/images/icon_alert-xs.png +0 -0
  122. package/stat/assets/images/icon_book.svg +12 -0
  123. package/stat/assets/images/icon_bookmark.svg +12 -0
  124. package/stat/assets/images/icon_close-pop.png +0 -0
  125. package/stat/assets/images/icon_download.png +0 -0
  126. package/stat/assets/images/icon_gear.svg +14 -0
  127. package/stat/assets/images/icon_hamburger.svg +20 -0
  128. package/stat/assets/images/icon_home.png +0 -0
  129. package/stat/assets/images/icon_home.svg +21 -0
  130. package/stat/assets/images/icon_home_ia.png +0 -0
  131. package/stat/assets/images/icon_indicator.png +0 -0
  132. package/stat/assets/images/icon_info.svg +11 -0
  133. package/stat/assets/images/icon_one_page.svg +8 -0
  134. package/stat/assets/images/icon_pause.svg +1 -0
  135. package/stat/assets/images/icon_play.svg +1 -0
  136. package/stat/assets/images/icon_playback-rate.svg +15 -0
  137. package/stat/assets/images/icon_return.png +0 -0
  138. package/stat/assets/images/icon_search_button.svg +8 -0
  139. package/stat/assets/images/icon_share.svg +9 -0
  140. package/stat/assets/images/icon_skip-ahead.svg +6 -0
  141. package/stat/assets/images/icon_skip-back.svg +13 -0
  142. package/stat/assets/images/icon_speaker.svg +18 -0
  143. package/stat/assets/images/icon_speaker_open.svg +10 -0
  144. package/stat/assets/images/icon_thumbnails.svg +12 -0
  145. package/stat/assets/images/icon_toc.svg +5 -0
  146. package/stat/assets/images/icon_two_pages.svg +9 -0
  147. package/stat/assets/images/icon_zoomer.png +0 -0
  148. package/stat/assets/images/loading.gif +0 -0
  149. package/stat/assets/images/logo_icon.png +0 -0
  150. package/stat/assets/images/marker_chap-off.png +0 -0
  151. package/stat/assets/images/marker_chap-off.svg +11 -0
  152. package/stat/assets/images/marker_chap-off_ia.png +0 -0
  153. package/stat/assets/images/marker_chap-on.png +0 -0
  154. package/stat/assets/images/marker_chap-on.svg +11 -0
  155. package/stat/assets/images/marker_srch-on.svg +11 -0
  156. package/stat/assets/images/marker_srchchap-off.png +0 -0
  157. package/stat/assets/images/marker_srchchap-on.png +0 -0
  158. package/stat/assets/images/nav_control-dn.png +0 -0
  159. package/stat/assets/images/nav_control-dn_ia.png +0 -0
  160. package/stat/assets/images/nav_control-up.png +0 -0
  161. package/stat/assets/images/nav_control-up_ia.png +0 -0
  162. package/stat/assets/images/nav_control.png +0 -0
  163. package/stat/assets/images/one_page_mode_icon.png +0 -0
  164. package/stat/assets/images/paper-badge.png +0 -0
  165. package/stat/assets/images/print_icon.png +0 -0
  166. package/stat/assets/images/progressbar.gif +0 -0
  167. package/stat/assets/images/right_edges.png +0 -0
  168. package/stat/assets/images/slider.png +0 -0
  169. package/stat/assets/images/slider_ia.png +0 -0
  170. package/stat/assets/images/thumbnail_mode_icon.png +0 -0
  171. package/stat/assets/images/transparent.png +0 -0
  172. package/stat/assets/images/two_page_mode_icon.png +0 -0
  173. package/stat/assets/images/zoom_in_icon.png +0 -0
  174. package/stat/assets/images/zoom_out_icon.png +0 -0
  175. package/stat/css/BookReader.scss +89 -0
  176. package/stat/css/_BRBookmarks.scss +29 -0
  177. package/stat/css/_BRComponent.scss +13 -0
  178. package/stat/css/_BRfloat.scss +197 -0
  179. package/stat/css/_BRicon.scss +48 -0
  180. package/stat/css/_BRmain.scss +251 -0
  181. package/stat/css/_BRnav.scss +359 -0
  182. package/stat/css/_BRpages.scss +139 -0
  183. package/stat/css/_BRsearch.scss +226 -0
  184. package/stat/css/_BRtoolbar.scss +84 -0
  185. package/stat/css/_BRvendor.scss +5 -0
  186. package/stat/css/_MobileNav.scss +194 -0
  187. package/stat/css/_TextSelection.scss +32 -0
  188. package/stat/css/_colorbox.scss +52 -0
  189. package/stat/css/_controls.scss +253 -0
  190. package/stat/css/_icons.scss +121 -0
  191. package/stat/jquery-wrapper.js +4 -0
  192. package/stat/plugins/plugin.archive_analytics.js +86 -0
  193. package/stat/plugins/plugin.autoplay.js +129 -0
  194. package/stat/plugins/plugin.chapters.js +248 -0
  195. package/stat/plugins/plugin.iframe.js +48 -0
  196. package/stat/plugins/plugin.mobile_nav.js +288 -0
  197. package/stat/plugins/plugin.resume.js +68 -0
  198. package/stat/plugins/plugin.text_selection.js +291 -0
  199. package/stat/plugins/plugin.url.js +198 -0
  200. package/stat/plugins/plugin.vendor-fullscreen.js +247 -0
  201. package/stat/plugins/search/plugin.search.js +439 -0
  202. package/stat/plugins/search/view.js +439 -0
  203. package/stat/plugins/tts/AbstractTTSEngine.js +249 -0
  204. package/stat/plugins/tts/FestivalTTSEngine.js +169 -0
  205. package/stat/plugins/tts/PageChunk.js +107 -0
  206. package/stat/plugins/tts/PageChunkIterator.js +163 -0
  207. package/stat/plugins/tts/WebTTSEngine.js +357 -0
  208. package/stat/plugins/tts/plugin.tts.js +357 -0
  209. package/stat/plugins/tts/tooltip_dict.js +15 -0
  210. package/stat/plugins/tts/utils.js +91 -0
  211. package/stat/util/browserSniffing.js +30 -0
  212. package/stat/util/debouncer.js +26 -0
  213. package/stat/util/docCookies.js +67 -0
  214. package/stat/util/strings.js +34 -0
  215. package/tests/e2e/viewmode.test.js +30 -30
  216. package/tests/jest/BookReader/BookReaderPublicFunctions.test.js +64 -52
  217. package/tests/karma/BookNavigator/book-navigator.test.js +413 -108
  218. package/tests/karma/BookNavigator/bookmarks/bookmark-button.test.js +44 -0
  219. package/tests/karma/BookNavigator/downloads/downloads-provider.test.js +6 -3
  220. package/tests/karma/BookNavigator/search/search-provider.test.js +106 -6
  221. package/tests/karma/BookNavigator/search/search-results.test.js +0 -2
  222. package/tests/karma/BookNavigator/sharing/sharing-provider.test.js +29 -20
  223. package/tests/karma/BookNavigator/volumes/volumes-provider.test.js +41 -17
  224. package/.nvmrc +0 -1
  225. package/src/BookNavigator/assets/book-loader.js +0 -27
  226. package/src/ItemNavigator/ItemNavigator.js +0 -377
@@ -0,0 +1,1372 @@
1
+ // @ts-check
2
+ // effect.js gives acces to extra easing function (e.g. easeInSine)
3
+ import 'jquery-ui/ui/effect.js';
4
+ import { clamp } from './utils.js';
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
+
10
+ /** @typedef {import('../BookReader.js').default} BookReader */
11
+ /** @typedef {import('./BookModel.js').BookModel} BookModel */
12
+ /** @typedef {import('./BookModel.js').PageIndex} PageIndex */
13
+ /** @typedef {import('./options.js').BookReaderOptions} BookReaderOptions */
14
+ /** @typedef {import('./PageContainer.js').PageContainer} PageContainer */
15
+ /** @typedef {import('./ModeSmoothZoom').SmoothZoomable} SmoothZoomable */
16
+
17
+ /** @implements {SmoothZoomable} */
18
+ export class Mode2Up {
19
+ /**
20
+ * @param {BookReader} br
21
+ * @param {BookModel} bookModel
22
+ */
23
+ constructor(br, bookModel) {
24
+ this.br = br;
25
+ this.book = bookModel;
26
+
27
+ /** @type {HTMLDivElement} */
28
+ this.leafEdgeL = null;
29
+ /** @type {HTMLDivElement} */
30
+ this.leafEdgeR = null;
31
+
32
+ /** @type {{ [index: number]: PageContainer }} */
33
+ this.pageContainers = {};
34
+
35
+ /** @type {ModeSmoothZoom} */
36
+ this.smoothZoomer = null;
37
+ this._scale = 1;
38
+ this.scaleCenter = { x: 0.5, y: 0.5 };
39
+ }
40
+
41
+ get $container() {
42
+ return this.br.refs.$brContainer[0];
43
+ }
44
+ get $visibleWorld() {
45
+ return this.br.refs.$brTwoPageView?.[0];
46
+ }
47
+
48
+ get scale() { return this._scale; }
49
+ set scale(newVal) {
50
+ this.$visibleWorld.style.transform = `scale(${newVal})`;
51
+ this.updateViewportOnZoom(newVal, this._scale);
52
+ this._scale = newVal;
53
+ }
54
+
55
+ /**
56
+ * @param {PageIndex} index
57
+ */
58
+ jumpToIndex(index) {
59
+ // By checking against min/max we do nothing if requested index
60
+ // is current
61
+ if (index < Math.min(this.br.twoPage.currentIndexL, this.br.twoPage.currentIndexR)) {
62
+ this.flipBackToIndex(index);
63
+ } else if (index > Math.max(this.br.twoPage.currentIndexL, this.br.twoPage.currentIndexR)) {
64
+ this.flipFwdToIndex(index);
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Draws book spread,
70
+ * sets event handlers,
71
+ * sets: `this.br.displayedIndices`
72
+ * updates toolbar zoom
73
+ * Important: `this.br.refs.$brTwoPageView` parent container must be emptied before calling
74
+ */
75
+ drawLeafs() {
76
+ const $twoPageViewEl = this.br.refs.$brTwoPageView;
77
+ const indexL = this.br.twoPage.currentIndexL;
78
+ const indexR = this.br.twoPage.currentIndexR;
79
+
80
+ this.createPageContainer(indexL).$container
81
+ .css(this.leftLeafCss)
82
+ .appendTo($twoPageViewEl);
83
+ this.createPageContainer(indexR).$container
84
+ .css(this.rightLeafCss)
85
+ .appendTo($twoPageViewEl);
86
+
87
+ this.displayedIndices = [this.br.twoPage.currentIndexL, this.br.twoPage.currentIndexR];
88
+ this.br.displayedIndices = this.displayedIndices;
89
+ this.br.updateToolbarZoom(this.br.reduce);
90
+ this.br.trigger('pageChanged');
91
+ }
92
+
93
+ /**
94
+ * @param {1} direction
95
+ */
96
+ zoom(direction) {
97
+ this.br.stopFlipAnimations();
98
+
99
+ // Recalculate autofit factors
100
+ this.calculateReductionFactors();
101
+
102
+ // Get new zoom state
103
+ const reductionFactor = this.br.nextReduce(this.br.reduce, direction, this.br.twoPage.reductionFactors);
104
+ if ((this.br.reduce == reductionFactor.reduce) && (this.br.twoPage.autofit == reductionFactor.autofit)) {
105
+ // Same zoom
106
+ return;
107
+ }
108
+ this.br.twoPage.autofit = reductionFactor.autofit;
109
+ this.br.reduce = reductionFactor.reduce;
110
+ this.br.pageScale = this.br.reduce; // preserve current reduce
111
+
112
+ // Preserve view center position
113
+ const oldCenter = this.getViewCenter();
114
+
115
+ // Prepare view with new center to minimize visual glitches
116
+ const drawNewSpread = true;
117
+ this.prepareTwoPageView(oldCenter.percentageX, oldCenter.percentageY, drawNewSpread);
118
+ }
119
+
120
+ /**
121
+ * Resize spread containers, does not prefetch
122
+ * uses `this.br.twoPage` properties
123
+ */
124
+ resizeSpread() {
125
+ this.br.resizeBRcontainer(false); // no animation
126
+ this.calculateSpreadSize();
127
+
128
+ this.br.refs?.$brTwoPageView.css(this.mainContainerCss);
129
+ this.centerView(); // let function self adjust
130
+
131
+ $(this.br.twoPage.coverDiv).css(this.spreadCoverCss); // click sheath is memoized somehow
132
+ const $spreadLayers = this.br.refs.$brTwoPageView;
133
+
134
+ $spreadLayers.find('.BRleafEdgeR')?.css(this.leafEdgeRCss);
135
+ $spreadLayers.find('.BRleafEdgeL')?.css(this.leafEdgeLCss);
136
+ $spreadLayers.find('.BRgutter')?.css(this.spineCss);
137
+
138
+ const indexL = this.br.twoPage.currentIndexL;
139
+ const indexR = this.br.twoPage.currentIndexR;
140
+ this.pageContainers[indexL].$container.css(this.leftLeafCss);
141
+ this.pageContainers[indexR].$container.css(this.rightLeafCss);
142
+ }
143
+
144
+ /**
145
+ * @param {number} centerPercentageX
146
+ * @param {number} centerPercentageY
147
+ * @param {Boolean} drawNewSpread
148
+ */
149
+ prepareTwoPageView(centerPercentageX, centerPercentageY, drawNewSpread = false) {
150
+ // Some decisions about two page view:
151
+ //
152
+ // Both pages will be displayed at the same height, even if they were different physical/scanned
153
+ // sizes. This simplifies the animation (from a design as well as technical standpoint). We
154
+ // examine the page aspect ratios (in calculateSpreadSize) and use the page with the most "normal"
155
+ // aspect ratio to determine the height.
156
+ //
157
+ // The two page view div is resized to keep the middle of the book in the middle of the div
158
+ // even as the page sizes change. To e.g. keep the middle of the book in the middle of the BRcontent
159
+ // div requires adjusting the offset of BRtwpageview and/or scrolling in BRcontent.
160
+ const startingReduce = this.br.reduce;
161
+ const startingIndices = this.br.displayedIndices;
162
+
163
+ this.br.refs.$brContainer.empty();
164
+ this.br.refs.$brContainer.css('overflow', 'auto');
165
+
166
+ // We want to display two facing pages. We may be missing
167
+ // one side of the spread because it is the first/last leaf,
168
+ // foldouts, missing pages, etc
169
+
170
+ const targetLeaf = clamp(this.br.firstIndex, this.br.firstDisplayableIndex(), this.br.lastDisplayableIndex());
171
+ const currentSpreadIndices = this.book.getSpreadIndices(targetLeaf);
172
+ this.br.twoPage.currentIndexL = currentSpreadIndices[0];
173
+ this.br.twoPage.currentIndexR = currentSpreadIndices[1];
174
+
175
+ this.calculateSpreadSize(); //sets this.br.reduce, twoPage.width, twoPage.height and others
176
+
177
+ /* check if calculations have changed that warrant a new book draw */
178
+ const sameReducer = startingReduce == this.br.reduce;
179
+ const sameStart = startingIndices == this.br.displayedIndices;
180
+ const hasNewDisplayPagesOrDimensions = !sameStart || (sameStart && !sameReducer);
181
+
182
+ if (drawNewSpread || hasNewDisplayPagesOrDimensions) {
183
+ this.prunePageContainers();
184
+ this.prefetch();
185
+ }
186
+
187
+ // Add the two page view
188
+ // $$$ Can we get everything set up and then append?
189
+ this.br.refs.$brTwoPageView = this.br.refs.$brTwoPageView || $('<div class="BRtwopageview"></div>');
190
+ const $twoPageViewEl = this.br.refs.$brTwoPageView;
191
+ $twoPageViewEl.empty();
192
+ $twoPageViewEl[0].style.transformOrigin = '0 0';
193
+ this.br.refs.$brContainer.append($twoPageViewEl);
194
+
195
+ // Attaches to first child, so must come after we add the page view
196
+ this.dragScrollable = this.dragScrollable || new DragScrollable(this.br.refs.$brContainer[0], {
197
+ preventDefault: true,
198
+ // Only handle mouse events; let browser/HammerJS handle touch
199
+ dragstart: 'mousedown',
200
+ dragcontinue: 'mousemove',
201
+ dragend: 'mouseup',
202
+ });
203
+
204
+ this.attachMouseHandlers();
205
+
206
+ // $$$ calculate container size first
207
+ this.br.refs?.$brTwoPageView.css(this.mainContainerCss);
208
+
209
+ // This will trump the incoming coordinates
210
+ // in order to center book when zooming out
211
+ if (this.br.twoPage.totalWidth < this.br.refs.$brContainer.prop('clientWidth')) {
212
+ centerPercentageX = 0.5;
213
+ }
214
+ if (this.br.twoPage.totalHeight < this.br.refs.$brContainer.prop('clientHeight')) {
215
+ centerPercentageY = 0.5;
216
+ }
217
+
218
+ this.centerView(centerPercentageX, centerPercentageY);
219
+
220
+ // then set
221
+ this.br.twoPage.coverDiv = document.createElement('div');
222
+ $(this.br.twoPage.coverDiv).attr('class', 'BRbookcover').css(this.spreadCoverCss).appendTo(this.br.refs.$brTwoPageView);
223
+
224
+ this.leafEdgeR = document.createElement('div');
225
+ this.leafEdgeR.className = 'BRleafEdgeR';
226
+ $(this.leafEdgeR).css(this.leafEdgeRCss).appendTo(this.br.refs.$brTwoPageView);
227
+
228
+ this.leafEdgeL = document.createElement('div');
229
+ this.leafEdgeL.className = 'BRleafEdgeL';
230
+ $(this.leafEdgeL).css(this.leafEdgeLCss).appendTo(this.br.refs.$brTwoPageView);
231
+
232
+ const div = document.createElement('div');
233
+ $(div).attr('class', 'BRgutter').css(this.spineCss).appendTo(this.br.refs.$brTwoPageView);
234
+
235
+ this.preparePopUp();
236
+
237
+ this.br.displayedIndices = [];
238
+
239
+ this.drawLeafs();
240
+ this.br.updateToolbarZoom(this.br.reduce);
241
+ this.br.updateBrClasses();
242
+
243
+ this.smoothZoomer = this.smoothZoomer || new ModeSmoothZoom(this);
244
+ this.smoothZoomer.attach();
245
+
246
+ this.htmlDimensionsCacher = this.htmlDimensionsCacher || new HTMLDimensionsCacher(this.$container);
247
+ }
248
+
249
+ unprepare() {
250
+ // Mode2Up attaches these listeners to the main BR container, so we need to
251
+ // detach these or it will cause issues for the other modes.
252
+ this.smoothZoomer.detach();
253
+ }
254
+
255
+ /**
256
+ * @param {number} newScale
257
+ * @param {number} oldScale
258
+ */
259
+ updateViewportOnZoom(newScale, oldScale) {
260
+ const container = this.br.refs.$brContainer[0];
261
+ const { scrollTop: T, scrollLeft: L } = container;
262
+ const W = this.htmlDimensionsCacher.clientWidth;
263
+ const H = this.htmlDimensionsCacher.clientHeight;
264
+
265
+ // Scale factor change
266
+ const F = newScale / oldScale;
267
+
268
+ // Where in the viewport the zoom is centered on
269
+ const XPOS = this.scaleCenter.x;
270
+ const YPOS = this.scaleCenter.y;
271
+ const oldCenter = {
272
+ x: L + XPOS * W,
273
+ y: T + YPOS * H,
274
+ };
275
+ const newCenter = {
276
+ x: F * oldCenter.x,
277
+ y: F * oldCenter.y,
278
+ };
279
+ container.scrollTop = newCenter.y - YPOS * H;
280
+ container.scrollLeft = newCenter.x - XPOS * W;
281
+
282
+ // Also update the visible page containers to load in highres if necessary
283
+ this.pageContainers[this.br.twoPage.currentIndexL]?.update({ reduce: this.br.reduce / newScale });
284
+ this.pageContainers[this.br.twoPage.currentIndexR]?.update({ reduce: this.br.reduce / newScale });
285
+ }
286
+
287
+ prunePageContainers() {
288
+ for (const index in this.pageContainers) {
289
+ if ((index != this.br.twoPage.currentIndexL) && (index != this.br.twoPage.currentIndexR)) {
290
+ $(this.pageContainers[index].$container).remove();
291
+ }
292
+ if ((index < this.br.twoPage.currentIndexL - 4) || (index > this.br.twoPage.currentIndexR + 4)) {
293
+ delete this.pageContainers[index];
294
+ }
295
+ }
296
+ }
297
+
298
+ /**
299
+ * This function prepares the "View Page n" popup that shows while the mouse is
300
+ * over the left/right "stack of sheets" edges. It also binds the mouse
301
+ * events for these divs.
302
+ */
303
+ preparePopUp() {
304
+ this.br.twoPagePopUp = document.createElement('div');
305
+ this.br.twoPagePopUp.className = 'BRtwoPagePopUp';
306
+ $(this.br.twoPagePopUp).css({
307
+ zIndex: '1000'
308
+ }).appendTo(this.br.refs.$brContainer);
309
+ $(this.br.twoPagePopUp).hide();
310
+
311
+ const leafEdges = [
312
+ {
313
+ $leafEdge: $(this.leafEdgeL),
314
+ /** @type {function(number): PageIndex} */
315
+ jumpIndexForPageX: this.jumpIndexForLeftEdgePageX.bind(this),
316
+ leftOffset: () => -$(this.br.twoPagePopUp).width() + 120,
317
+ },
318
+ {
319
+ $leafEdge: $(this.leafEdgeR),
320
+ /** @type {function(number): PageIndex} */
321
+ jumpIndexForPageX: this.jumpIndexForRightEdgePageX.bind(this),
322
+ leftOffset: () => -120,
323
+ },
324
+ ];
325
+
326
+ for (const { $leafEdge, jumpIndexForPageX, leftOffset } of leafEdges) {
327
+ $leafEdge.on('mouseenter', () => $(this.br.twoPagePopUp).show());
328
+ $leafEdge.on('mouseleave', () => $(this.br.twoPagePopUp).hide());
329
+
330
+ $leafEdge.on('click', e => {
331
+ this.br.trigger(EVENTS.stop);
332
+ this.br.jumpToIndex(jumpIndexForPageX(e.pageX));
333
+ });
334
+
335
+ $leafEdge.on('mousemove', e => {
336
+ const jumpIndex = clamp(jumpIndexForPageX(e.pageX), 0, this.book.getNumLeafs() - 1);
337
+ $(this.br.twoPagePopUp).text(`View ${this.book.getPageName(jumpIndex)}`);
338
+
339
+ // $$$ TODO: Make sure popup is positioned so that it is in view
340
+ // (https://bugs.edge.launchpad.net/gnubook/+bug/327456)
341
+ $(this.br.twoPagePopUp).css({
342
+ left: `${e.pageX - this.br.refs.$brContainer.offset().left + this.br.refs.$brContainer.scrollLeft() + leftOffset()}px`,
343
+ top: `${e.pageY - this.br.refs.$brContainer.offset().top + this.br.refs.$brContainer.scrollTop()}px`
344
+ });
345
+ });
346
+ }
347
+ }
348
+
349
+ setSpreadIndices() {
350
+ const targetLeaf = clamp(this.br.firstIndex, this.br.firstDisplayableIndex(), this.br.lastDisplayableIndex());
351
+ const currentSpreadIndices = this.book.getSpreadIndices(targetLeaf);
352
+ this.br.twoPage.currentIndexL = currentSpreadIndices[0];
353
+ this.br.twoPage.currentIndexR = currentSpreadIndices[1];
354
+ }
355
+
356
+ /**
357
+ * Calculates 2-page spread dimensions based on this.br.twoPage.currentIndexL and
358
+ * this.br.twoPage.currentIndexR
359
+ * This function sets this.br.twoPage.height, twoPage.width
360
+ */
361
+ calculateSpreadSize() {
362
+ const firstIndex = this.br.twoPage.currentIndexL;
363
+ const secondIndex = this.br.twoPage.currentIndexR;
364
+
365
+ // Calculate page sizes and total leaf width
366
+ let spreadSize;
367
+ if ( this.br.twoPage.autofit) {
368
+ spreadSize = this.getIdealSpreadSize(firstIndex, secondIndex);
369
+ } else {
370
+ // set based on reduction factor
371
+ spreadSize = this.getSpreadSizeFromReduce(firstIndex, secondIndex, this.br.reduce);
372
+ }
373
+ // Both pages together
374
+ this.br.twoPage.height = spreadSize.height || 0;
375
+ this.br.twoPage.width = spreadSize.width || 0;
376
+
377
+ // Individual pages
378
+ this.br.twoPage.scaledWL = this.getPageWidth(firstIndex) || 0;
379
+ this.br.twoPage.scaledWR = this.getPageWidth(secondIndex) || 0;
380
+
381
+ // Leaf edges
382
+ this.br.twoPage.edgeWidth = spreadSize.totalLeafEdgeWidth; // The combined width of both edges
383
+ this.br.twoPage.leafEdgeWidthL = this.br.leafEdgeWidth(this.br.twoPage.currentIndexL);
384
+ this.br.twoPage.leafEdgeWidthR = this.br.twoPage.edgeWidth - this.br.twoPage.leafEdgeWidthL;
385
+
386
+
387
+ // Book cover
388
+ // The width of the book cover div. The combined width of both pages, twice the width
389
+ // of the book cover internal padding (2*10) and the page edges
390
+ this.br.twoPage.bookCoverDivWidth = this.coverWidth(this.br.twoPage.scaledWL + this.br.twoPage.scaledWR);
391
+ // The height of the book cover div
392
+ this.br.twoPage.bookCoverDivHeight = this.br.twoPage.height + 2 * this.br.twoPage.coverInternalPadding;
393
+
394
+
395
+ // We calculate the total width and height for the div so that we can make the book
396
+ // spine centered
397
+ const leftGutterOffset = this.gutterOffsetForIndex(firstIndex);
398
+ const leftWidthFromCenter = this.br.twoPage.scaledWL - leftGutterOffset + this.br.twoPage.leafEdgeWidthL;
399
+ const rightWidthFromCenter = this.br.twoPage.scaledWR + leftGutterOffset + this.br.twoPage.leafEdgeWidthR;
400
+ const largestWidthFromCenter = Math.max( leftWidthFromCenter, rightWidthFromCenter );
401
+ this.br.twoPage.totalWidth = 2 * (largestWidthFromCenter + this.br.twoPage.coverInternalPadding + this.br.twoPage.coverExternalPadding);
402
+ this.br.twoPage.totalHeight = this.br.twoPage.height + 2 * (this.br.twoPage.coverInternalPadding + this.br.twoPage.coverExternalPadding);
403
+
404
+ // We want to minimize the unused space in two-up mode (maximize the amount of page
405
+ // shown). We give width to the leaf edges and these widths change (though the sum
406
+ // of the two remains constant) as we flip through the book. With the book
407
+ // cover centered and fixed in the BRcontainer div the page images will meet
408
+ // at the "gutter" which is generally offset from the center.
409
+ this.br.twoPage.middle = this.br.twoPage.totalWidth >> 1;
410
+ this.br.twoPage.gutter = this.br.twoPage.middle + this.gutterOffsetForIndex(firstIndex);
411
+
412
+ // The left edge of the book cover moves depending on the width of the pages
413
+ // $$$ change to getter
414
+ this.br.twoPage.bookCoverDivLeft = this.br.twoPage.gutter - this.br.twoPage.scaledWL - this.br.twoPage.leafEdgeWidthL - this.br.twoPage.coverInternalPadding;
415
+ // The top edge of the book cover stays a fixed distance from the top
416
+ this.br.twoPage.bookCoverDivTop = this.br.twoPage.coverExternalPadding;
417
+
418
+ // Book spine
419
+ this.br.twoPage.bookSpineDivHeight = this.br.twoPage.height + 2 * this.br.twoPage.coverInternalPadding;
420
+ this.br.twoPage.bookSpineDivLeft = this.br.twoPage.middle - (this.br.twoPage.bookSpineDivWidth >> 1);
421
+ this.br.twoPage.bookSpineDivTop = this.br.twoPage.bookCoverDivTop;
422
+
423
+ this.br.reduce = spreadSize.reduce < 0 ? this.br.reduce : spreadSize.reduce; // $$$ really set this here?
424
+ }
425
+
426
+ /**
427
+ *
428
+ * @param {number} firstIndex
429
+ * @param {number} secondIndex
430
+ * @return {{ width: number, height: number, totalLeafEdgeWidth: number, reduce: number}}
431
+ */
432
+ getIdealSpreadSize(firstIndex, secondIndex) {
433
+ const ideal = {};
434
+
435
+ // We check which page is closest to a "normal" page and use that to set the height
436
+ // for both pages. This means that foldouts and other odd size pages will be displayed
437
+ // smaller than the nominal zoom amount.
438
+ const canon5Dratio = 1.5;
439
+
440
+ const first = {
441
+ height: this.book._getPageHeight(firstIndex),
442
+ width: this.book._getPageWidth(firstIndex)
443
+ };
444
+
445
+ const second = {
446
+ height: this.book._getPageHeight(secondIndex),
447
+ width: this.book._getPageWidth(secondIndex)
448
+ };
449
+
450
+ const firstIndexRatio = first.height / first.width;
451
+ const secondIndexRatio = second.height / second.width;
452
+
453
+ let ratio;
454
+ if (Math.abs(firstIndexRatio - canon5Dratio) < Math.abs(secondIndexRatio - canon5Dratio)) {
455
+ ratio = firstIndexRatio;
456
+ } else {
457
+ ratio = secondIndexRatio;
458
+ }
459
+
460
+ const totalLeafEdgeWidth = Math.floor(this.book.getNumLeafs() * 0.1);
461
+ const maxLeafEdgeWidth = Math.floor(this.br.refs.$brContainer.prop('clientWidth') * 0.1);
462
+ ideal.totalLeafEdgeWidth = Math.min(totalLeafEdgeWidth, maxLeafEdgeWidth);
463
+
464
+ const widthOutsidePages = 2 * (this.br.twoPage.coverInternalPadding + this.br.twoPage.coverExternalPadding) + ideal.totalLeafEdgeWidth;
465
+ const heightOutsidePages = 2 * (this.br.twoPage.coverInternalPadding + this.br.twoPage.coverExternalPadding);
466
+
467
+ ideal.width = (this.br.refs.$brContainer.width() - widthOutsidePages) >> 1;
468
+ ideal.width = ideal.width > 10 ? ideal.width - 10 : 1; // $$$ fudge factor
469
+
470
+ ideal.height = this.br.refs.$brContainer.height() - heightOutsidePages;
471
+ ideal.height = ideal.height > 15 ? ideal.height - 15 : 1; // $$$ fudge factor
472
+
473
+ if (ideal.height / ratio <= ideal.width) {
474
+ //use height
475
+ ideal.width = Math.floor(ideal.height / ratio) || 1;
476
+ } else {
477
+ //use width
478
+ ideal.height = Math.floor(ideal.width * ratio) || 1;
479
+ }
480
+
481
+ // $$$ check this logic with large spreads
482
+ ideal.reduce = Math.round(((first.height + second.height) / 2) / ideal.height);
483
+
484
+ return ideal;
485
+ }
486
+
487
+ /**
488
+ * Returns the spread size calculated from the reduction factor for the given pages
489
+ * @param {number} firstIndex
490
+ * @param {number} secondIndex
491
+ * @return {Object}
492
+ */
493
+ getSpreadSizeFromReduce(firstIndex, secondIndex, reduce) {
494
+ const spreadSize = {};
495
+ // $$$ Scale this based on reduce?
496
+ const totalLeafEdgeWidth = Math.floor(this.book.getNumLeafs() * 0.1);
497
+ // $$$ Assumes leaf edge width constant at all zoom levels
498
+ const maxLeafEdgeWidth = Math.floor(this.br.refs.$brContainer.prop('clientWidth') * 0.1);
499
+ spreadSize.totalLeafEdgeWidth = Math.min(totalLeafEdgeWidth, maxLeafEdgeWidth);
500
+
501
+ // $$$ Possibly incorrect -- we should make height "dominant"
502
+ const nativeWidth = this.book._getPageWidth(firstIndex) + this.book._getPageWidth(secondIndex);
503
+ const nativeHeight = this.book._getPageHeight(firstIndex) + this.book._getPageHeight(secondIndex);
504
+ spreadSize.height = Math.floor( (nativeHeight / 2) / this.br.reduce );
505
+ spreadSize.width = Math.floor( (nativeWidth / 2) / this.br.reduce );
506
+ spreadSize.reduce = reduce;
507
+
508
+ return spreadSize;
509
+ }
510
+
511
+ /**
512
+ * Returns the current ideal reduction factor
513
+ * @return {number}
514
+ */
515
+ getAutofitReduce() {
516
+ const spreadSize = this.getIdealSpreadSize(this.br.twoPage.currentIndexL, this.br.twoPage.currentIndexR);
517
+ return spreadSize.reduce;
518
+ }
519
+
520
+ /**
521
+ * Returns true if the pages extend past the edge of the view
522
+ * @deprecated slated for deprecation by v5.0.0
523
+ * @return {boolean}
524
+ */
525
+ isZoomedIn() {
526
+ let isZoomedIn = false;
527
+ if (this.br.twoPage.autofit != 'auto') {
528
+ if (this.br.reduce < this.getAutofitReduce()) {
529
+ isZoomedIn = true;
530
+ }
531
+ }
532
+ return isZoomedIn;
533
+ }
534
+
535
+ calculateReductionFactors() {
536
+ this.br.twoPage.reductionFactors = this.br.reductionFactors.concat([
537
+ {
538
+ reduce: this.getIdealSpreadSize( this.br.twoPage.currentIndexL, this.br.twoPage.currentIndexR ).reduce,
539
+ autofit: 'auto'
540
+ }
541
+ ]);
542
+ this.br.twoPage.reductionFactors.sort(this.br._reduceSort);
543
+ }
544
+
545
+ /**
546
+ * Set the cursor for two page view
547
+ * @deprecated Since version 4.3.3. Will be deleted in version 5.0
548
+ */
549
+ setCursor() {
550
+ console.warn('Call to deprecated method, Mode2Up.setCursor. No-op.');
551
+ }
552
+
553
+ /**
554
+ * @param {Number|null} index to flip back one spread, pass index=null
555
+ */
556
+ flipBackToIndex(index) {
557
+ if (this.br.constMode1up == this.br.mode) return;
558
+ if (this.br.animating) return;
559
+
560
+ if (null != this.br.leafEdgeTmp) {
561
+ alert('error: leafEdgeTmp should be null!');
562
+ return;
563
+ }
564
+
565
+ if (null == index) {
566
+ const {currentIndexL, currentIndexR} = this.br.twoPage;
567
+ const minDisplayedIndex = Math.min(currentIndexL, currentIndexR);
568
+ const prev = this.book.getPage(minDisplayedIndex).findPrev({ combineConsecutiveUnviewables: true });
569
+ if (!prev) return;
570
+ index = prev.index;
571
+ // Can only flip to a left page
572
+ // (downstream code handles index = -1, so this is ok I guess)
573
+ if (prev.pageSide == 'R') index--;
574
+ }
575
+
576
+ this.br.updateNavIndexThrottled(index);
577
+
578
+ const previousIndices = this.book.getSpreadIndices(index);
579
+
580
+ if (previousIndices[0] < this.br.firstDisplayableIndex() || previousIndices[1] < this.br.firstDisplayableIndex()) {
581
+ return;
582
+ }
583
+
584
+ this.br.animating = true;
585
+
586
+ if ('rl' != this.br.pageProgression) {
587
+ // Assume LTR and we are going backward
588
+ this.prepareFlipLeftToRight(previousIndices[0], previousIndices[1]);
589
+ this.flipLeftToRight(previousIndices[0], previousIndices[1]);
590
+ } else {
591
+ // RTL and going backward
592
+ this.prepareFlipRightToLeft(previousIndices[0], previousIndices[1]);
593
+ this.flipRightToLeft(previousIndices[0], previousIndices[1]);
594
+ }
595
+ }
596
+
597
+ /**
598
+ * Flips the page on the left towards the page on the right
599
+ * @param {number} newIndexL
600
+ * @param {number} newIndexR
601
+ */
602
+ flipLeftToRight(newIndexL, newIndexR) {
603
+ this.br.refs.$brContainer.addClass("BRpageFlipping");
604
+ const leftLeaf = this.br.twoPage.currentIndexL;
605
+
606
+ const oldLeafEdgeWidthL = this.br.leafEdgeWidth(this.br.twoPage.currentIndexL);
607
+ const newLeafEdgeWidthL = this.br.leafEdgeWidth(newIndexL);
608
+ const leafEdgeTmpW = oldLeafEdgeWidthL - newLeafEdgeWidthL;
609
+
610
+ const currWidthL = this.getPageWidth(leftLeaf);
611
+ const newWidthL = this.getPageWidth(newIndexL);
612
+ const newWidthR = this.getPageWidth(newIndexR);
613
+
614
+ const top = this.top();
615
+ const gutter = this.br.twoPage.middle + this.gutterOffsetForIndex(newIndexL);
616
+
617
+ //animation strategy:
618
+ // 0. remove search highlight, if any.
619
+ // 1. create a new div, called leafEdgeTmp to represent the leaf edge between the leftmost edge
620
+ // of the left leaf and where the user clicked in the leaf edge.
621
+ // Note that if this function was triggered by left() and not a
622
+ // mouse click, the width of leafEdgeTmp is very small (zero px).
623
+ // 2. animate both leafEdgeTmp to the gutter (without changing its width) and animate
624
+ // leftLeaf to width=0.
625
+ // 3. When step 2 is finished, animate leafEdgeTmp to right-hand side of new right leaf
626
+ // (left=gutter+newWidthR) while also animating the new right leaf from width=0 to
627
+ // its new full width.
628
+ // 4. After step 3 is finished, do the following:
629
+ // - remove leafEdgeTmp from the dom.
630
+ // - resize and move the right leaf edge (leafEdgeR) to left=gutter+newWidthR
631
+ // and width=twoPage.edgeWidth-newLeafEdgeWidthL.
632
+ // - resize and move the left leaf edge (leafEdgeL) to left=gutter-newWidthL-newLeafEdgeWidthL
633
+ // and width=newLeafEdgeWidthL.
634
+ // - resize the back cover (twoPage.coverDiv) to left=gutter-newWidthL-newLeafEdgeWidthL-10
635
+ // and width=newWidthL+newWidthR+twoPage.edgeWidth+20
636
+ // - move new left leaf (newIndexL) forward to zindex=2 so it can receive clicks.
637
+ // - remove old left and right leafs from the dom [prunePageContainers()].
638
+ // - prefetch new adjacent leafs.
639
+ // - set up click handlers for both new left and right leafs.
640
+ // - redraw the search highlight.
641
+ // - update the pagenum box and the url.
642
+
643
+ const $twoPageViewEl = this.br.refs.$brTwoPageView;
644
+ const leftEdgeTmpLeft = gutter - currWidthL - leafEdgeTmpW;
645
+
646
+ this.br.leafEdgeTmp = document.createElement('div');
647
+ this.br.leafEdgeTmp.className = 'BRleafEdgeTmp';
648
+ $(this.br.leafEdgeTmp).css({
649
+ width: `${leafEdgeTmpW}px`,
650
+ height: `${this.br.twoPage.height}px`,
651
+ left: `${leftEdgeTmpLeft}px`,
652
+ top: `${top}px`,
653
+ zIndex: 1000,
654
+ }).appendTo($twoPageViewEl);
655
+
656
+ $(this.leafEdgeL).css({
657
+ width: `${newLeafEdgeWidthL}px`,
658
+ left: `${gutter - currWidthL - newLeafEdgeWidthL}px`
659
+ });
660
+
661
+ // Left gets the offset of the current left leaf from the document
662
+ const left = this.pageContainers[leftLeaf].$container.offset().left;
663
+ // $$$ This seems very similar to the gutter. May be able to consolidate the logic.
664
+ const right = `${$twoPageViewEl.prop('clientWidth') - left - this.pageContainers[leftLeaf].$container.width() + $twoPageViewEl.offset().left - 2}px`;
665
+
666
+ // We change the left leaf to right positioning
667
+ // $$$ This causes animation glitches during resize. See https://bugs.edge.launchpad.net/gnubook/+bug/328327
668
+ this.pageContainers[leftLeaf].$container.css({
669
+ right,
670
+ left: ''
671
+ });
672
+
673
+ $(this.br.leafEdgeTmp).animate({left: gutter}, this.br.flipSpeed, 'easeInSine');
674
+
675
+ this.pageContainers[leftLeaf].$container.animate({width: '0px'}, this.br.flipSpeed, 'easeInSine', () => {
676
+
677
+ $(this.br.leafEdgeTmp).animate({left: `${gutter + newWidthR}px`}, this.br.flipSpeed, 'easeOutSine');
678
+
679
+ this.br.$('.BRgutter').css({left: `${gutter - this.br.twoPage.bookSpineDivWidth * 0.5}px`});
680
+
681
+ this.pageContainers[newIndexR].$container.animate({width: `${newWidthR}px`}, this.br.flipSpeed, 'easeOutSine', () => {
682
+ this.pageContainers[newIndexL].$container.css('zIndex', 2);
683
+
684
+ $(this.leafEdgeR).css({
685
+ // Moves the right leaf edge
686
+ width: `${this.br.twoPage.edgeWidth - newLeafEdgeWidthL}px`,
687
+ left: `${gutter + newWidthR}px`
688
+ });
689
+
690
+ $(this.leafEdgeL).css({
691
+ // Moves and resizes the left leaf edge
692
+ width: `${newLeafEdgeWidthL}px`,
693
+ left: `${gutter - newWidthL - newLeafEdgeWidthL}px`
694
+ });
695
+
696
+ // Resizes the brown border div
697
+ $(this.br.twoPage.coverDiv).css({
698
+ width: `${this.coverWidth(newWidthL + newWidthR)}px`,
699
+ left: `${gutter - newWidthL - newLeafEdgeWidthL - this.br.twoPage.coverInternalPadding}px`
700
+ });
701
+
702
+ $(this.br.leafEdgeTmp).remove();
703
+ this.br.leafEdgeTmp = null;
704
+
705
+ // $$$ TODO refactor with opposite direction flip
706
+
707
+ this.br.twoPage.currentIndexL = newIndexL;
708
+ this.br.twoPage.currentIndexR = newIndexR;
709
+ this.br.twoPage.scaledWL = newWidthL;
710
+ this.br.twoPage.scaledWR = newWidthR;
711
+ this.br.twoPage.gutter = gutter;
712
+
713
+ this.br.updateFirstIndex(this.br.twoPage.currentIndexL);
714
+ this.br.displayedIndices = [newIndexL, newIndexR];
715
+ this.prunePageContainers();
716
+ this.br.animating = false;
717
+
718
+ this.resizeSpread();
719
+
720
+ if (this.br.animationFinishedCallback) {
721
+ this.br.animationFinishedCallback();
722
+ this.br.animationFinishedCallback = null;
723
+ }
724
+
725
+ this.br.refs.$brContainer.removeClass("BRpageFlipping");
726
+ this.br.textSelectionPlugin?.stopPageFlip(this.br.refs.$brContainer);
727
+ this.centerView();
728
+ this.br.trigger('pageChanged');
729
+
730
+ // get next previous batch immediately
731
+ this.prunePageContainers();
732
+ this.createPageContainer(newIndexL - 2);
733
+ this.createPageContainer(newIndexR - 2);
734
+ this.createPageContainer(newIndexL - 3);
735
+ this.createPageContainer(newIndexR - 3);
736
+ });
737
+ });
738
+ }
739
+
740
+ /**
741
+ * @param {PageIndex} index
742
+ */
743
+ createPageContainer(index) {
744
+ if (!this.pageContainers[index]) {
745
+ this.pageContainers[index] = this.br._createPageContainer(index);
746
+ }
747
+ this.pageContainers[index].update({ reduce: this.br.reduce / this.scale });
748
+ return this.pageContainers[index];
749
+ }
750
+
751
+ /**
752
+ * Whether we flip left or right is dependent on the page progression
753
+ * to flip forward one spread, pass index=null
754
+ * @param {number} index
755
+ */
756
+ flipFwdToIndex(index) {
757
+ if (this.br.animating) return;
758
+
759
+ if (null != this.br.leafEdgeTmp) {
760
+ alert('error: leafEdgeTmp should be null!');
761
+ return;
762
+ }
763
+
764
+ if (null == index) {
765
+ // Need to use the max here, since it could be a right to left book
766
+ const {currentIndexL, currentIndexR} = this.br.twoPage;
767
+ const maxDisplayedIndex = Math.max(currentIndexL, currentIndexR);
768
+ const nextPage = this.book.getPage(maxDisplayedIndex).findNext({ combineConsecutiveUnviewables: true });
769
+ if (!nextPage) return;
770
+ index = nextPage.index;
771
+ }
772
+ if (index > this.br.lastDisplayableIndex()) return;
773
+
774
+ this.br.updateNavIndexThrottled(index);
775
+
776
+ this.br.animating = true;
777
+
778
+ const nextIndices = this.book.getSpreadIndices(index);
779
+
780
+ if ('rl' != this.br.pageProgression) {
781
+ // We did not specify RTL
782
+ this.prepareFlipRightToLeft(nextIndices[0], nextIndices[1]);
783
+ this.flipRightToLeft(nextIndices[0], nextIndices[1]);
784
+ } else {
785
+ // RTL
786
+ this.prepareFlipLeftToRight(nextIndices[0], nextIndices[1]);
787
+ this.flipLeftToRight(nextIndices[0], nextIndices[1]);
788
+ }
789
+ }
790
+
791
+ /**
792
+ * Flip from left to right and show the nextL and nextR indices on those sides
793
+ * $$$ better not to have to pass gutter in
794
+ * @param {number} newIndexL
795
+ * @param {number} newIndexR
796
+ */
797
+ flipRightToLeft(newIndexL, newIndexR) {
798
+ this.br.refs.$brContainer.addClass("BRpageFlipping");
799
+
800
+ const oldLeafEdgeWidthL = this.br.leafEdgeWidth(this.br.twoPage.currentIndexL);
801
+ const oldLeafEdgeWidthR = this.br.twoPage.edgeWidth - oldLeafEdgeWidthL;
802
+ const newLeafEdgeWidthL = this.br.leafEdgeWidth(newIndexL);
803
+ const newLeafEdgeWidthR = this.br.twoPage.edgeWidth - newLeafEdgeWidthL;
804
+
805
+ const leafEdgeTmpW = oldLeafEdgeWidthR - newLeafEdgeWidthR;
806
+
807
+ const top = this.top();
808
+ const scaledW = this.getPageWidth(this.br.twoPage.currentIndexR);
809
+
810
+ const middle = this.br.twoPage.middle;
811
+ const gutter = middle + this.gutterOffsetForIndex(newIndexL);
812
+
813
+ const $twoPageViewEl = this.br.refs.$brTwoPageView;
814
+
815
+ this.br.leafEdgeTmp = document.createElement('div');
816
+ this.br.leafEdgeTmp.className = 'BRleafEdgeTmp';
817
+ $(this.br.leafEdgeTmp).css({
818
+ width: `${leafEdgeTmpW}px`,
819
+ height: `${this.br.twoPage.height}px`,
820
+ left: `${gutter + scaledW}px`,
821
+ top: `${top}px`,
822
+ zIndex:1000
823
+ }).appendTo($twoPageViewEl);
824
+
825
+ const newWidthL = this.getPageWidth(newIndexL);
826
+ const newWidthR = this.getPageWidth(newIndexR);
827
+
828
+ $(this.leafEdgeR).css({width: `${newLeafEdgeWidthR}px`, left: `${gutter + newWidthR}px` });
829
+ const speed = this.br.flipSpeed;
830
+
831
+ $(this.br.leafEdgeTmp).animate({left: gutter}, speed, 'easeInSine');
832
+ this.pageContainers[this.br.twoPage.currentIndexR].$container.animate({width: '0px'}, speed, 'easeInSine', () => {
833
+ this.br.$('BRgutter').css({left: `${gutter - this.br.twoPage.bookSpineDivWidth * 0.5}px`});
834
+ $(this.br.leafEdgeTmp).animate({left: `${gutter - newWidthL - leafEdgeTmpW}px`}, speed, 'easeOutSine');
835
+ this.pageContainers[newIndexL].$container.animate({width: `${newWidthL}px`}, speed, 'easeOutSine', () => {
836
+ this.pageContainers[newIndexR].$container.css('zIndex', 2);
837
+
838
+ $(this.leafEdgeL).css({
839
+ width: `${newLeafEdgeWidthL}px`,
840
+ left: `${gutter - newWidthL - newLeafEdgeWidthL}px`
841
+ });
842
+
843
+ // Resizes the book cover
844
+ $(this.br.twoPage.coverDiv).css({
845
+ width: `${this.coverWidth(newWidthL + newWidthR)}px`,
846
+ left: `${gutter - newWidthL - newLeafEdgeWidthL - this.br.twoPage.coverInternalPadding}px`
847
+ });
848
+
849
+ $(this.br.leafEdgeTmp).remove();
850
+ this.br.leafEdgeTmp = null;
851
+
852
+ this.br.twoPage.currentIndexL = newIndexL;
853
+ this.br.twoPage.currentIndexR = newIndexR;
854
+ this.br.twoPage.scaledWL = newWidthL;
855
+ this.br.twoPage.scaledWR = newWidthR;
856
+ this.br.twoPage.gutter = gutter;
857
+
858
+ this.br.updateFirstIndex(this.br.twoPage.currentIndexL);
859
+ this.br.displayedIndices = [newIndexL, newIndexR];
860
+ this.prunePageContainers();
861
+ this.br.animating = false;
862
+
863
+ this.resizeSpread();
864
+
865
+ if (this.br.animationFinishedCallback) {
866
+ this.br.animationFinishedCallback();
867
+ this.br.animationFinishedCallback = null;
868
+ }
869
+
870
+ this.br.refs.$brContainer.removeClass("BRpageFlipping");
871
+ this.br.textSelectionPlugin?.stopPageFlip(this.br.refs.$brContainer);
872
+ this.centerView();
873
+ this.br.trigger('pageChanged');
874
+
875
+ this.prunePageContainers();
876
+ this.createPageContainer(newIndexL + 2);
877
+ this.createPageContainer(newIndexR + 2);
878
+ this.createPageContainer(newIndexL + 3);
879
+ this.createPageContainer(newIndexR + 3);
880
+ });
881
+ });
882
+ }
883
+
884
+ attachMouseHandlers() {
885
+ this.br.refs.$brTwoPageView
886
+ .off('mouseup').on('mouseup', ev => {
887
+ if (ev.which == 3) {
888
+ // right click
889
+ return !this.br.protected;
890
+ }
891
+
892
+ const $page = $(ev.target).closest('.BRpagecontainer');
893
+ if ($page.data('side') == 'L') this.br.left();
894
+ else if ($page.data('side') == 'R') this.br.right();
895
+ });
896
+ }
897
+
898
+ /**
899
+ * Prepare to flip the left page towards the right. This corresponds to moving
900
+ * backward when the page progression is left to right.
901
+ * @param {number} prevL
902
+ * @param {number} prevR
903
+ */
904
+ prepareFlipLeftToRight(prevL, prevR) {
905
+ this.createPageContainer(prevL, true);
906
+ this.createPageContainer(prevR, true);
907
+
908
+ const $twoPageViewEl = this.br.refs.$brTwoPageView;
909
+ const height = this.book._getPageHeight(prevL);
910
+ const width = this.book._getPageWidth(prevL);
911
+ const middle = this.br.twoPage.middle;
912
+ const top = this.top();
913
+ const scaledW = this.br.twoPage.height * width / height; // $$$ assumes height of page is dominant
914
+
915
+ // The gutter is the dividing line between the left and right pages.
916
+ // It is offset from the middle to create the illusion of thickness to the pages
917
+ const gutter = middle + this.gutterOffsetForIndex(prevL);
918
+
919
+ const leftCSS = {
920
+ left: `${gutter - scaledW}px`,
921
+ right: '', // clear right property
922
+ top: `${top}px`,
923
+ height: this.br.twoPage.height,
924
+ width: `${scaledW}px`,
925
+ zIndex: 1
926
+ };
927
+
928
+ this.pageContainers[prevL].$container
929
+ .css(leftCSS)
930
+ .appendTo($twoPageViewEl);
931
+
932
+ const rightCSS = {
933
+ left: `${gutter}px`,
934
+ right: '',
935
+ top: `${top}px`,
936
+ height: this.br.twoPage.height,
937
+ width: '0',
938
+ zIndex: 2
939
+ };
940
+
941
+ this.pageContainers[prevR].$container
942
+ .css(rightCSS)
943
+ .appendTo($twoPageViewEl);
944
+ }
945
+
946
+ /**
947
+ * // $$$ mang we're adding an extra pixel in the middle. See https://bugs.edge.launchpad.net/gnubook/+bug/411667
948
+ */
949
+ prepareFlipRightToLeft(nextL, nextR) {
950
+ this.createPageContainer(nextL, true);
951
+ this.createPageContainer(nextR, true);
952
+
953
+ const $twoPageViewEl = this.br.refs.$brTwoPageView;
954
+ let height = this.book._getPageHeight(nextR);
955
+ let width = this.book._getPageWidth(nextR);
956
+ const middle = this.br.twoPage.middle;
957
+ const top = this.top();
958
+ let scaledW = this.br.twoPage.height * width / height;
959
+
960
+ const gutter = middle + this.gutterOffsetForIndex(nextL);
961
+
962
+ $(this.pageContainers[nextR].$container).css({
963
+ left: `${gutter}px`,
964
+ top: `${top}px`,
965
+ height: this.br.twoPage.height,
966
+ width: `${scaledW}px`,
967
+ zIndex: 1,
968
+ })
969
+ .appendTo($twoPageViewEl);
970
+
971
+ height = this.book._getPageHeight(nextL);
972
+ width = this.book._getPageWidth(nextL);
973
+ scaledW = this.br.twoPage.height * width / height;
974
+
975
+ $(this.pageContainers[nextL].$container).css({
976
+ right: `${$twoPageViewEl.prop('clientWidth') - gutter}px`,
977
+ top: `${top}px`,
978
+ height: this.br.twoPage.height,
979
+ width: '0px', // Start at 0 width, then grow to the left
980
+ zIndex: 2,
981
+ })
982
+ .appendTo($twoPageViewEl);
983
+ }
984
+
985
+ getPageWidth(index) {
986
+ // We return the width based on the dominant height
987
+ const height = this.book._getPageHeight(index);
988
+ const width = this.book._getPageWidth(index);
989
+ // $$$ we assume width is relative to current spread
990
+ return Math.floor(this.br.twoPage.height * width / height);
991
+ }
992
+
993
+ /**
994
+ * Returns the position of the gutter (line between the page images)
995
+ */
996
+ gutter() {
997
+ return this.br.twoPage.middle + this.gutterOffsetForIndex(this.br.twoPage.currentIndexL);
998
+ }
999
+
1000
+ /**
1001
+ * Returns the offset for the top of the page images
1002
+ */
1003
+ top() {
1004
+ return this.br.twoPage.coverExternalPadding + this.br.twoPage.coverInternalPadding; // $$$ + border?
1005
+ }
1006
+
1007
+ /**
1008
+ * Returns the width of the cover div given the total page width
1009
+ * @param {number} totalPageWidth
1010
+ * @return {number}
1011
+ */
1012
+ coverWidth(totalPageWidth) {
1013
+ return totalPageWidth + this.br.twoPage.edgeWidth + 2 * this.br.twoPage.coverInternalPadding;
1014
+ }
1015
+
1016
+ /**
1017
+ * Returns the percentage offset into twopageview div at the center of container div
1018
+ */
1019
+ getViewCenter() {
1020
+ const { $brContainer, $brTwoPageView } = this.br.refs;
1021
+ const center = {};
1022
+
1023
+ const containerOffset = $brContainer.offset();
1024
+ const viewOffset = $brTwoPageView.offset();
1025
+ center.percentageX = (containerOffset.left - viewOffset.left + ($brContainer.prop('clientWidth') >> 1)) / this.br.twoPage.totalWidth;
1026
+ center.percentageY = (containerOffset.top - viewOffset.top + ($brContainer.prop('clientHeight') >> 1)) / this.br.twoPage.totalHeight;
1027
+
1028
+ return center;
1029
+ }
1030
+
1031
+ /**
1032
+ * Centers the point given by percentage from left,top of twopageview
1033
+ * @param {number} [percentageX=0.5]
1034
+ * @param {number} [percentageY=0.5]
1035
+ */
1036
+ centerView(percentageX, percentageY) {
1037
+
1038
+ if ('undefined' == typeof(percentageX)) {
1039
+ percentageX = 0.5;
1040
+ }
1041
+ if ('undefined' == typeof(percentageY)) {
1042
+ percentageY = 0.5;
1043
+ }
1044
+
1045
+ const viewWidth = this.br.refs.$brTwoPageView.width();
1046
+ const containerClientWidth = this.br.refs.$brContainer.prop('clientWidth');
1047
+ const intoViewX = percentageX * viewWidth;
1048
+
1049
+ const viewHeight = this.br.refs.$brTwoPageView.height();
1050
+ const containerClientHeight = this.br.refs.$brContainer.prop('clientHeight');
1051
+ const intoViewY = percentageY * viewHeight;
1052
+
1053
+ if (viewWidth < containerClientWidth) {
1054
+ // Can fit width without scrollbars - center by adjusting offset
1055
+ this.br.refs.$brTwoPageView.css('left', `${(containerClientWidth >> 1) - intoViewX}px`);
1056
+ } else {
1057
+ // Need to scroll to center
1058
+ this.br.refs.$brTwoPageView.css('left', 0);
1059
+ this.br.refs.$brContainer.scrollLeft(intoViewX - (containerClientWidth >> 1));
1060
+ }
1061
+
1062
+ if (viewHeight < containerClientHeight) {
1063
+ // Fits with scrollbars - add offset
1064
+ this.br.refs.$brTwoPageView.css('top', `${(containerClientHeight >> 1) - intoViewY}px`);
1065
+ } else {
1066
+ this.br.refs.$brTwoPageView.css('top', 0);
1067
+ this.br.refs.$brContainer.scrollTop(intoViewY - (containerClientHeight >> 1));
1068
+ }
1069
+ }
1070
+
1071
+ /**
1072
+ * Returns the integer height of the click-to-flip areas at the edges of the book
1073
+ * @return {number}
1074
+ */
1075
+ flipAreaHeight() {
1076
+ return Math.floor(this.br.twoPage.height);
1077
+ }
1078
+
1079
+ /**
1080
+ * Returns the the integer width of the flip areas
1081
+ * @return {number}
1082
+ */
1083
+ flipAreaWidth() {
1084
+ const max = 100; // $$$ TODO base on view width?
1085
+ const min = 10;
1086
+
1087
+ const width = this.br.twoPage.width * 0.15;
1088
+ return Math.floor(clamp(width, min, max));
1089
+ }
1090
+
1091
+ /**
1092
+ * Returns integer top offset for flip areas
1093
+ * @return {number}
1094
+ */
1095
+ flipAreaTop() {
1096
+ return Math.floor(this.br.twoPage.bookCoverDivTop + this.br.twoPage.coverInternalPadding);
1097
+ }
1098
+
1099
+ /**
1100
+ * Left offset for left flip area
1101
+ * @return {number}
1102
+ */
1103
+ leftFlipAreaLeft() {
1104
+ return Math.floor(this.br.twoPage.gutter - this.br.twoPage.scaledWL);
1105
+ }
1106
+
1107
+ /**
1108
+ * Left offset for right flip area
1109
+ * @return {number}
1110
+ */
1111
+ rightFlipAreaLeft() {
1112
+ return Math.floor(this.br.twoPage.gutter + this.br.twoPage.scaledWR - this.flipAreaWidth());
1113
+ }
1114
+
1115
+ /**
1116
+ * Position calculation shared between search and text-to-speech functions
1117
+ */
1118
+ setHilightCss(div, index, left, right, top, bottom) {
1119
+ // We calculate the reduction factor for the specific page because it can be different
1120
+ // for each page in the spread
1121
+ const height = this.book._getPageHeight(index);
1122
+ const width = this.book._getPageWidth(index);
1123
+ const reduce = this.br.twoPage.height / height;
1124
+ const scaledW = Math.floor(width * reduce);
1125
+
1126
+ const gutter = this.gutter();
1127
+ let pageL;
1128
+ if ('L' == this.book.getPageSide(index)) {
1129
+ pageL = gutter - scaledW;
1130
+ } else {
1131
+ pageL = gutter;
1132
+ }
1133
+ const pageT = this.top();
1134
+
1135
+ $(div).css({
1136
+ width: `${(right - left) * reduce}px`,
1137
+ height: `${(bottom - top) * reduce}px`,
1138
+ left: `${pageL + left * reduce}px`,
1139
+ top: `${pageT + top * reduce}px`
1140
+ });
1141
+ }
1142
+
1143
+ /**
1144
+ * Returns the gutter offset for the spread containing the given index.
1145
+ * This function supports RTL
1146
+ * @param {number} pindex
1147
+ * @return {number}
1148
+ */
1149
+ gutterOffsetForIndex(pindex) {
1150
+ // To find the offset of the gutter from the middle we calculate our percentage distance
1151
+ // through the book (0..1), remap to (-0.5..0.5) and multiply by the total page edge width
1152
+ let offset = Math.floor(((pindex / this.book.getNumLeafs()) - 0.5) * this.br.twoPage.edgeWidth);
1153
+
1154
+ // But then again for RTL it's the opposite
1155
+ if ('rl' == this.br.pageProgression) {
1156
+ offset *= -1;
1157
+ }
1158
+
1159
+ return offset;
1160
+ }
1161
+
1162
+ /**
1163
+ * Returns the width of the leaf edge div for the page with index given
1164
+ * @param {number} pindex
1165
+ * @return {number}
1166
+ */
1167
+ leafEdgeWidth(pindex) {
1168
+ // $$$ could there be single pixel rounding errors for L vs R?
1169
+ if ((this.book.getPageSide(pindex) == 'L') && (this.br.pageProgression != 'rl')) {
1170
+ return Math.floor( (pindex / this.book.getNumLeafs()) * this.br.twoPage.edgeWidth + 0.5);
1171
+ } else {
1172
+ return Math.floor( (1 - pindex / this.book.getNumLeafs()) * this.br.twoPage.edgeWidth + 0.5);
1173
+ }
1174
+ }
1175
+
1176
+ /**
1177
+ * Returns the target jump leaf given a page coordinate (inside the left page edge div)
1178
+ * @param {number} pageX
1179
+ * @return {PageIndex}
1180
+ */
1181
+ jumpIndexForLeftEdgePageX(pageX) {
1182
+ let jumpIndex;
1183
+ if ('rl' != this.br.pageProgression) {
1184
+ // LTR - flipping backward
1185
+ jumpIndex = this.br.twoPage.currentIndexL - ($(this.leafEdgeL).offset().left + $(this.leafEdgeL).width() - pageX) * 10;
1186
+
1187
+ // browser may have resized the div due to font size change -- see https://bugs.launchpad.net/gnubook/+bug/333570
1188
+ jumpIndex = clamp(Math.round(jumpIndex), this.br.firstDisplayableIndex(), this.br.twoPage.currentIndexL - 2);
1189
+ return jumpIndex;
1190
+
1191
+ } else {
1192
+ jumpIndex = this.br.twoPage.currentIndexL + ($(this.leafEdgeL).offset().left + $(this.leafEdgeL).width() - pageX) * 10;
1193
+ jumpIndex = clamp(Math.round(jumpIndex), this.br.twoPage.currentIndexL + 2, this.br.lastDisplayableIndex());
1194
+ return jumpIndex;
1195
+ }
1196
+ }
1197
+
1198
+ /**
1199
+ * Returns the target jump leaf given a page coordinate (inside the right page edge div)
1200
+ * @param {number} pageX
1201
+ * @return {PageIndex}
1202
+ */
1203
+ jumpIndexForRightEdgePageX(pageX) {
1204
+ let jumpIndex;
1205
+ if ('rl' != this.br.pageProgression) {
1206
+ // LTR
1207
+ jumpIndex = this.br.twoPage.currentIndexL + (pageX - $(this.leafEdgeR).offset().left) * 10;
1208
+ jumpIndex = clamp(Math.round(jumpIndex), this.br.twoPage.currentIndexL + 2, this.br.lastDisplayableIndex());
1209
+ return jumpIndex;
1210
+ } else {
1211
+ jumpIndex = this.br.twoPage.currentIndexL - (pageX - $(this.leafEdgeR).offset().left) * 10;
1212
+ jumpIndex = clamp(Math.round(jumpIndex), this.br.firstDisplayableIndex(), this.br.twoPage.currentIndexL - 2);
1213
+ return jumpIndex;
1214
+ }
1215
+ }
1216
+
1217
+ /**
1218
+ * Fetches the currently displayed images (if not already fetching)
1219
+ * as wells as any nearby pages.
1220
+ */
1221
+ prefetch() {
1222
+ // $$$ We should check here if the current indices have finished
1223
+ // loading (with some timeout) before loading more page images
1224
+ // See https://bugs.edge.launchpad.net/bookreader/+bug/511391
1225
+ const { max, min } = Math;
1226
+ const { book } = this;
1227
+ const { currentIndexL, currentIndexR } = this.br.twoPage;
1228
+ const ADJACENT_PAGES_TO_LOAD = 2;
1229
+ // currentIndexL can be -1; getPage returns the last page of the book
1230
+ // when given -1, so need to prevent that.
1231
+ let lowPage = book.getPage(max(0, min(currentIndexL, currentIndexR)));
1232
+ let highPage = book.getPage(max(currentIndexL, currentIndexR));
1233
+
1234
+ for (let i = 0; i < ADJACENT_PAGES_TO_LOAD + 2; i++) {
1235
+ if (lowPage) {
1236
+ this.createPageContainer(lowPage.index);
1237
+ lowPage = lowPage.findPrev({ combineConsecutiveUnviewables: true });
1238
+ }
1239
+
1240
+ if (highPage) {
1241
+ this.createPageContainer(highPage.index);
1242
+ highPage = highPage.findNext({ combineConsecutiveUnviewables: true });
1243
+ }
1244
+ }
1245
+ }
1246
+
1247
+ /* 2up Container Sizes */
1248
+
1249
+ /** main positions for inner containers */
1250
+ get baseLeafCss() {
1251
+ return {
1252
+ position: 'absolute',
1253
+ right: '',
1254
+ top: `${this.top()}px`,
1255
+ zIndex: 2,
1256
+ };
1257
+ }
1258
+
1259
+ /** main height for inner containers */
1260
+ get heightCss() {
1261
+ return {
1262
+ height: `${this.br.twoPage.height}px`, // $$$ height forced the same for both pages
1263
+ };
1264
+ }
1265
+
1266
+ /** Left Page sizing */
1267
+ get leftLeafCss() {
1268
+ return {
1269
+ ...this.baseLeafCss,
1270
+ ...this.heightCss,
1271
+ left: `${this.br.twoPage.gutter - this.br.twoPage.scaledWL}px`,
1272
+ width: `${this.br.twoPage.scaledWL}px`,
1273
+ };
1274
+ }
1275
+
1276
+ /** Left side book thickness */
1277
+ get leafEdgeLCss() {
1278
+ return {
1279
+ ...this.heightCss,
1280
+ width: `${this.br.twoPage.leafEdgeWidthL}px`,
1281
+ left: `${this.br.twoPage.bookCoverDivLeft + this.br.twoPage.coverInternalPadding}px`,
1282
+ top: `${this.br.twoPage.bookCoverDivTop + this.br.twoPage.coverInternalPadding}px`,
1283
+ border: this.br.twoPage.leafEdgeWidthL === 0 ? 'none' : null
1284
+ };
1285
+ }
1286
+
1287
+ /** Right Page sizing */
1288
+ get rightLeafCss() {
1289
+ return {
1290
+ ...this.baseLeafCss,
1291
+ ...this.heightCss,
1292
+ left: `${this.br.twoPage.gutter}px`,
1293
+ width: `${this.br.twoPage.scaledWR}px`,
1294
+ };
1295
+ }
1296
+
1297
+ /** Right side book thickness */
1298
+ get leafEdgeRCss() {
1299
+ return {
1300
+ ...this.heightCss,
1301
+ width: `${this.br.twoPage.leafEdgeWidthR}px`,
1302
+ left: `${this.br.twoPage.scaledWL + this.br.twoPage.scaledWR + this.br.twoPage.leafEdgeWidthL}px`,
1303
+ top: `${this.br.twoPage.bookCoverDivTop + this.br.twoPage.coverInternalPadding}px`,
1304
+ border: this.br.twoPage.leafEdgeWidthR === 0 ? 'none' : null
1305
+ };
1306
+ }
1307
+
1308
+ /** main container sizing */
1309
+ get mainContainerCss() {
1310
+ return {
1311
+ height: `${this.br.twoPage.totalHeight}px`,
1312
+ width: `${this.br.twoPage.totalWidth}px`,
1313
+ position: 'absolute'
1314
+ };
1315
+ }
1316
+
1317
+ /** book cover sizing */
1318
+ get spreadCoverCss() {
1319
+ return {
1320
+ width: `${this.br.twoPage.bookCoverDivWidth}px`,
1321
+ height: `${this.br.twoPage.bookCoverDivHeight}px`,
1322
+ visibility: 'visible'
1323
+ };
1324
+ }
1325
+
1326
+ /** book spine sizing */
1327
+ get spineCss() {
1328
+ return {
1329
+ width: `${this.br.twoPage.bookSpineDivWidth}px`,
1330
+ height: `${this.br.twoPage.bookSpineDivHeight}px`,
1331
+ left: `${this.br.twoPage.gutter - (this.br.twoPage.bookSpineDivWidth / 2)}px`,
1332
+ top: `${this.br.twoPage.bookSpineDivTop}px`
1333
+ };
1334
+ }
1335
+ /** end CSS */
1336
+ }
1337
+
1338
+ /**
1339
+ * @implements {BookReaderOptions["twoPage"]}
1340
+ * @typedef {object} TwoPageState
1341
+ * @property {number} coverInternalPadding
1342
+ * @property {number} coverExternalPadding
1343
+ *
1344
+ * @property {import('./options.js').AutoFitValues} autofit
1345
+ * @property {number} width
1346
+ * @property {number} height
1347
+ * @property {number} currentIndexL
1348
+ * @property {number} currentIndexR
1349
+ * @property {number} scaledWL
1350
+ * @property {number} scaledWR
1351
+ * @property {number} gutter
1352
+ * @property {Array<{reduce: number, autofit: import('./options.js').AutoFitValues}>} reductionFactors
1353
+ * @property {number} totalHeight
1354
+ * @property {number} totalWidth
1355
+ *
1356
+ * @property {HTMLDivElement} coverDiv
1357
+ * @property {number} bookCoverDivTop
1358
+ * @property {number} bookCoverDivLeft
1359
+ * @property {number} bookCoverDivWidth
1360
+ * @property {number} bookCoverDivHeight
1361
+ *
1362
+ * @property {number} leafEdgeWidthL
1363
+ * @property {number} leafEdgeWidthR
1364
+ *
1365
+ * @property {number} bookSpineDivTop
1366
+ * @property {number} bookSpineDivLeft
1367
+ * @property {number} bookSpineDivWidth
1368
+ * @property {number} bookSpineDivHeight
1369
+ *
1370
+ * @property {number} edgeWidth
1371
+ * @property {number} middle
1372
+ */