@internetarchive/bookreader 5.0.0-8 → 5.0.0-80

Sign up to get free protection for your applications and to get access to all the features.
Files changed (313) hide show
  1. package/.eslintrc.js +17 -15
  2. package/.github/workflows/node.js.yml +73 -10
  3. package/.github/workflows/npm-publish.yml +6 -20
  4. package/.testcaferc.js +10 -0
  5. package/BookReader/BookReader.css +398 -1133
  6. package/BookReader/BookReader.js +1 -1
  7. package/BookReader/BookReader.js.LICENSE.txt +20 -20
  8. package/BookReader/BookReader.js.map +1 -1
  9. package/BookReader/ia-bookreader-bundle.js +1782 -0
  10. package/BookReader/ia-bookreader-bundle.js.LICENSE.txt +7 -0
  11. package/BookReader/ia-bookreader-bundle.js.map +1 -0
  12. package/BookReader/icons/1up.svg +1 -1
  13. package/BookReader/icons/2up.svg +1 -1
  14. package/BookReader/icons/advance.svg +1 -1
  15. package/BookReader/icons/chevron-right.svg +1 -1
  16. package/BookReader/icons/close-circle-dark.svg +1 -1
  17. package/BookReader/icons/close-circle.svg +1 -1
  18. package/BookReader/icons/fullscreen.svg +1 -1
  19. package/BookReader/icons/fullscreen_exit.svg +1 -1
  20. package/BookReader/icons/hamburger.svg +1 -1
  21. package/BookReader/icons/left-arrow.svg +1 -1
  22. package/BookReader/icons/magnify-minus.svg +1 -1
  23. package/BookReader/icons/magnify-plus.svg +1 -1
  24. package/BookReader/icons/magnify.svg +1 -1
  25. package/BookReader/icons/pause.svg +1 -1
  26. package/BookReader/icons/play.svg +1 -1
  27. package/BookReader/icons/playback-speed.svg +1 -1
  28. package/BookReader/icons/read-aloud.svg +1 -1
  29. package/BookReader/icons/review.svg +1 -1
  30. package/BookReader/icons/thumbnails.svg +1 -1
  31. package/BookReader/icons/voice.svg +1 -0
  32. package/BookReader/icons/volume-full.svg +1 -1
  33. package/BookReader/images/BRicons.svg +3 -3
  34. package/BookReader/images/books_graphic.svg +1 -1
  35. package/BookReader/images/icon_book.svg +1 -1
  36. package/BookReader/images/icon_bookmark.svg +1 -1
  37. package/BookReader/images/icon_gear.svg +1 -1
  38. package/BookReader/images/icon_hamburger.svg +1 -1
  39. package/BookReader/images/icon_home.svg +1 -1
  40. package/BookReader/images/icon_info.svg +1 -1
  41. package/BookReader/images/icon_one_page.svg +1 -1
  42. package/BookReader/images/icon_pause.svg +1 -1
  43. package/BookReader/images/icon_play.svg +1 -1
  44. package/BookReader/images/icon_playback-rate.svg +1 -1
  45. package/BookReader/images/icon_search_button.svg +1 -1
  46. package/BookReader/images/icon_share.svg +1 -1
  47. package/BookReader/images/icon_skip-ahead.svg +1 -1
  48. package/BookReader/images/icon_skip-back.svg +1 -1
  49. package/BookReader/images/icon_speaker.svg +1 -1
  50. package/BookReader/images/icon_speaker_open.svg +1 -1
  51. package/BookReader/images/icon_thumbnails.svg +1 -1
  52. package/BookReader/images/icon_toc.svg +1 -1
  53. package/BookReader/images/icon_two_pages.svg +1 -1
  54. package/BookReader/images/marker_chap-off.svg +1 -1
  55. package/BookReader/images/marker_chap-on.svg +1 -1
  56. package/BookReader/images/marker_srch-on.svg +1 -1
  57. package/BookReader/jquery-3.js +2 -0
  58. package/BookReader/jquery-3.js.LICENSE.txt +24 -0
  59. package/BookReader/plugins/plugin.archive_analytics.js +1 -1
  60. package/BookReader/plugins/plugin.archive_analytics.js.map +1 -1
  61. package/BookReader/plugins/plugin.autoplay.js +1 -1
  62. package/BookReader/plugins/plugin.autoplay.js.map +1 -1
  63. package/BookReader/plugins/plugin.chapters.js +25 -1
  64. package/BookReader/plugins/plugin.chapters.js.LICENSE.txt +1 -0
  65. package/BookReader/plugins/plugin.chapters.js.map +1 -1
  66. package/BookReader/plugins/plugin.iframe.js +1 -1
  67. package/BookReader/plugins/plugin.iframe.js.map +1 -1
  68. package/BookReader/plugins/plugin.iiif.js +2 -0
  69. package/BookReader/plugins/plugin.iiif.js.map +1 -0
  70. package/BookReader/plugins/plugin.resume.js +1 -1
  71. package/BookReader/plugins/plugin.resume.js.map +1 -1
  72. package/BookReader/plugins/plugin.search.js +2 -1
  73. package/BookReader/plugins/plugin.search.js.LICENSE.txt +1 -0
  74. package/BookReader/plugins/plugin.search.js.map +1 -1
  75. package/BookReader/plugins/plugin.text_selection.js +2 -1
  76. package/BookReader/plugins/plugin.text_selection.js.LICENSE.txt +1 -0
  77. package/BookReader/plugins/plugin.text_selection.js.map +1 -1
  78. package/BookReader/plugins/plugin.tts.js +1 -1
  79. package/BookReader/plugins/plugin.tts.js.LICENSE.txt +2 -0
  80. package/BookReader/plugins/plugin.tts.js.map +1 -1
  81. package/BookReader/plugins/plugin.url.js +1 -1
  82. package/BookReader/plugins/plugin.url.js.map +1 -1
  83. package/BookReader/plugins/plugin.vendor-fullscreen.js +1 -1
  84. package/BookReader/plugins/plugin.vendor-fullscreen.js.map +1 -1
  85. package/BookReader/webcomponents-bundle.js +3 -0
  86. package/BookReader/webcomponents-bundle.js.LICENSE.txt +9 -0
  87. package/BookReader/webcomponents-bundle.js.map +1 -0
  88. package/BookReaderDemo/BookReaderDemo.css +18 -19
  89. package/BookReaderDemo/BookReaderJSAdvanced.js +0 -3
  90. package/BookReaderDemo/BookReaderJSAutoplay.js +4 -1
  91. package/BookReaderDemo/BookReaderJSSimple.js +1 -0
  92. package/BookReaderDemo/IADemoBr.js +147 -0
  93. package/BookReaderDemo/demo-advanced.html +2 -2
  94. package/BookReaderDemo/demo-autoplay.html +2 -3
  95. package/BookReaderDemo/demo-embed-iframe-src.html +2 -1
  96. package/BookReaderDemo/demo-fullscreen-mobile.html +3 -5
  97. package/BookReaderDemo/demo-fullscreen.html +2 -4
  98. package/BookReaderDemo/demo-iiif.html +99 -12
  99. package/BookReaderDemo/demo-internetarchive.html +214 -18
  100. package/BookReaderDemo/demo-multiple.html +2 -1
  101. package/BookReaderDemo/demo-preview-pages.html +2 -1
  102. package/BookReaderDemo/demo-simple.html +2 -1
  103. package/BookReaderDemo/demo-vendor-fullscreen.html +2 -4
  104. package/BookReaderDemo/ia-multiple-volumes-manifest.js +170 -0
  105. package/BookReaderDemo/immersion-1up.html +2 -2
  106. package/BookReaderDemo/immersion-mode.html +2 -4
  107. package/BookReaderDemo/toggle_controls.html +3 -2
  108. package/BookReaderDemo/view_mode.html +2 -1
  109. package/BookReaderDemo/viewmode-cycle.html +2 -3
  110. package/CHANGELOG.md +536 -33
  111. package/README.md +14 -1
  112. package/babel.config.js +20 -0
  113. package/codecov.yml +6 -0
  114. package/index.html +4 -1
  115. package/jsconfig.json +19 -0
  116. package/netlify.toml +9 -0
  117. package/package.json +70 -60
  118. package/renovate.json +52 -0
  119. package/scripts/preversion.js +0 -1
  120. package/src/BookNavigator/assets/bookmark-colors.js +1 -1
  121. package/src/BookNavigator/assets/button-base.js +4 -2
  122. package/src/BookNavigator/assets/ia-logo.js +17 -0
  123. package/src/BookNavigator/assets/icon_checkmark.js +1 -1
  124. package/src/BookNavigator/assets/icon_close.js +1 -1
  125. package/src/BookNavigator/book-navigator.js +590 -0
  126. package/src/BookNavigator/bookmarks/bookmark-button.js +3 -2
  127. package/src/BookNavigator/bookmarks/bookmark-edit.js +3 -4
  128. package/src/BookNavigator/bookmarks/bookmarks-list.js +2 -3
  129. package/src/BookNavigator/bookmarks/bookmarks-loginCTA.js +3 -8
  130. package/src/BookNavigator/bookmarks/bookmarks-provider.js +27 -17
  131. package/src/BookNavigator/bookmarks/ia-bookmarks.js +116 -67
  132. package/src/BookNavigator/delete-modal-actions.js +1 -1
  133. package/src/BookNavigator/downloads/downloads-provider.js +36 -21
  134. package/src/BookNavigator/downloads/downloads.js +41 -25
  135. package/src/BookNavigator/search/search-provider.js +49 -27
  136. package/src/BookNavigator/search/search-results.js +23 -9
  137. package/src/BookNavigator/sharing.js +27 -0
  138. package/src/BookNavigator/viewable-files.js +95 -0
  139. package/src/BookNavigator/visual-adjustments/visual-adjustments-provider.js +11 -10
  140. package/src/BookNavigator/visual-adjustments/visual-adjustments.js +3 -3
  141. package/src/BookReader/BookModel.js +64 -34
  142. package/src/BookReader/DragScrollable.js +233 -0
  143. package/src/BookReader/Mode1Up.js +56 -351
  144. package/src/BookReader/Mode1UpLit.js +388 -0
  145. package/src/BookReader/Mode2Up.js +73 -1318
  146. package/src/BookReader/Mode2UpLit.js +776 -0
  147. package/src/BookReader/ModeCoordinateSpace.js +29 -0
  148. package/src/BookReader/ModeSmoothZoom.js +312 -0
  149. package/src/BookReader/ModeThumb.js +18 -12
  150. package/src/BookReader/Navbar/Navbar.js +14 -40
  151. package/src/BookReader/PageContainer.js +81 -6
  152. package/src/BookReader/ReduceSet.js +1 -1
  153. package/src/BookReader/Toolbar/Toolbar.js +10 -37
  154. package/src/BookReader/events.js +2 -3
  155. package/src/BookReader/options.js +27 -2
  156. package/src/BookReader/utils/HTMLDimensionsCacher.js +44 -0
  157. package/src/BookReader/utils/ScrollClassAdder.js +31 -0
  158. package/src/BookReader/utils/SelectionObserver.js +45 -0
  159. package/src/BookReader/utils.js +118 -13
  160. package/src/BookReader.js +427 -1061
  161. package/src/assets/icons/magnify-minus.svg +3 -7
  162. package/src/assets/icons/magnify-plus.svg +3 -7
  163. package/src/assets/icons/voice.svg +1 -0
  164. package/src/css/BookReader.scss +1 -5
  165. package/src/css/_BRBookmarks.scss +1 -1
  166. package/src/css/_BRComponent.scss +1 -1
  167. package/src/css/_BRmain.scss +16 -3
  168. package/src/css/_BRnav.scss +12 -39
  169. package/src/css/_BRpages.scss +149 -40
  170. package/src/css/_BRsearch.scss +68 -25
  171. package/src/css/_BRtoolbar.scss +5 -5
  172. package/src/css/_TextSelection.scss +87 -27
  173. package/src/css/_colorbox.scss +2 -2
  174. package/src/css/_controls.scss +20 -7
  175. package/src/css/_icons.scss +1 -1
  176. package/src/ia-bookreader/ia-bookreader.js +224 -0
  177. package/src/plugins/plugin.archive_analytics.js +3 -3
  178. package/src/plugins/plugin.autoplay.js +5 -11
  179. package/src/plugins/plugin.chapters.js +237 -191
  180. package/src/plugins/plugin.iiif.js +151 -0
  181. package/src/plugins/plugin.resume.js +3 -3
  182. package/src/plugins/plugin.text_selection.js +464 -134
  183. package/src/plugins/plugin.vendor-fullscreen.js +4 -4
  184. package/src/plugins/search/plugin.search.js +142 -125
  185. package/src/plugins/search/utils.js +43 -0
  186. package/src/plugins/search/view.js +33 -58
  187. package/src/plugins/tts/AbstractTTSEngine.js +71 -40
  188. package/src/plugins/tts/FestivalTTSEngine.js +13 -14
  189. package/src/plugins/tts/PageChunk.js +15 -21
  190. package/src/plugins/tts/PageChunkIterator.js +8 -12
  191. package/src/plugins/tts/WebTTSEngine.js +87 -71
  192. package/src/plugins/tts/plugin.tts.js +96 -127
  193. package/src/plugins/tts/utils.js +15 -25
  194. package/src/plugins/url/UrlPlugin.js +191 -0
  195. package/src/plugins/{plugin.url.js → url/plugin.url.js} +45 -16
  196. package/src/util/browserSniffing.js +22 -0
  197. package/src/util/docCookies.js +21 -2
  198. package/tests/e2e/README.md +37 -0
  199. package/tests/e2e/autoplay.test.js +2 -2
  200. package/tests/e2e/base.test.js +8 -16
  201. package/tests/e2e/helpers/base.js +53 -48
  202. package/tests/e2e/helpers/debug.js +1 -1
  203. package/tests/e2e/helpers/params.js +17 -0
  204. package/tests/e2e/helpers/rightToLeft.js +8 -14
  205. package/tests/e2e/helpers/search.js +73 -0
  206. package/tests/e2e/models/Navigation.js +20 -37
  207. package/tests/e2e/rightToLeft.test.js +4 -5
  208. package/tests/e2e/viewmode.test.js +40 -33
  209. package/tests/jest/BookNavigator/book-navigator.test.js +661 -0
  210. package/tests/jest/BookNavigator/bookmarks/bookmark-button.test.js +43 -0
  211. package/tests/{karma → jest}/BookNavigator/bookmarks/bookmark-edit.test.js +25 -26
  212. package/tests/{karma → jest}/BookNavigator/bookmarks/bookmarks-list.test.js +41 -42
  213. package/tests/jest/BookNavigator/bookmarks/ia-bookmarks.test.js +45 -0
  214. package/tests/jest/BookNavigator/downloads/downloads-provider.test.js +67 -0
  215. package/tests/jest/BookNavigator/downloads/downloads.test.js +53 -0
  216. package/tests/jest/BookNavigator/search/search-provider.test.js +167 -0
  217. package/tests/{karma → jest}/BookNavigator/search/search-results.test.js +109 -60
  218. package/tests/jest/BookNavigator/sharing/sharing-provider.test.js +49 -0
  219. package/tests/jest/BookNavigator/viewable-files/viewable-files-provider.test.js +80 -0
  220. package/tests/jest/BookNavigator/visual-adjustments.test.js +200 -0
  221. package/tests/{BookReader → jest/BookReader}/BookModel.test.js +74 -14
  222. package/tests/jest/BookReader/BookReaderPublicFunctions.test.js +193 -0
  223. package/tests/{BookReader → jest/BookReader}/ImageCache.test.js +4 -4
  224. package/tests/jest/BookReader/Mode1UpLit.test.js +73 -0
  225. package/tests/jest/BookReader/Mode2Up.test.js +98 -0
  226. package/tests/jest/BookReader/Mode2UpLit.test.js +190 -0
  227. package/tests/jest/BookReader/ModeCoordinateSpace.test.js +16 -0
  228. package/tests/jest/BookReader/ModeSmoothZoom.test.js +218 -0
  229. package/tests/jest/BookReader/ModeThumb.test.js +71 -0
  230. package/tests/{BookReader → jest/BookReader}/Navbar/Navbar.test.js +10 -10
  231. package/tests/{BookReader → jest/BookReader}/PageContainer.test.js +88 -6
  232. package/tests/{BookReader → jest/BookReader}/ReduceSet.test.js +1 -1
  233. package/tests/{BookReader → jest/BookReader}/Toolbar/Toolbar.test.js +2 -2
  234. package/tests/jest/BookReader/utils/HTMLDimensionsCacher.test.js +59 -0
  235. package/tests/jest/BookReader/utils/ScrollClassAdder.test.js +49 -0
  236. package/tests/jest/BookReader/utils/SelectionObserver.test.js +57 -0
  237. package/tests/{BookReader → jest/BookReader}/utils/classes.test.js +1 -1
  238. package/tests/jest/BookReader/utils.test.js +229 -0
  239. package/tests/jest/BookReader.keyboard.test.js +190 -0
  240. package/tests/{BookReader.options.test.js → jest/BookReader.options.test.js} +9 -1
  241. package/tests/{BookReader.test.js → jest/BookReader.test.js} +26 -37
  242. package/tests/{plugins → jest/plugins}/plugin.archive_analytics.test.js +2 -2
  243. package/tests/{plugins → jest/plugins}/plugin.autoplay.test.js +4 -4
  244. package/tests/jest/plugins/plugin.chapters.test.js +195 -0
  245. package/tests/{plugins → jest/plugins}/plugin.iframe.test.js +2 -2
  246. package/tests/{plugins → jest/plugins}/plugin.resume.test.js +3 -3
  247. package/tests/jest/plugins/plugin.text_selection.test.js +317 -0
  248. package/tests/{plugins → jest/plugins}/plugin.vendor-fullscreen.test.js +2 -2
  249. package/tests/{plugins → jest/plugins}/search/plugin.search.test.js +25 -47
  250. package/tests/{plugins → jest/plugins}/search/plugin.search.view.test.js +39 -6
  251. package/tests/jest/plugins/search/utils.js +25 -0
  252. package/tests/jest/plugins/search/utils.test.js +29 -0
  253. package/tests/{plugins → jest/plugins}/tts/AbstractTTSEngine.test.js +29 -9
  254. package/tests/{plugins → jest/plugins}/tts/FestivalTTSEngine.test.js +4 -4
  255. package/tests/{plugins → jest/plugins}/tts/PageChunk.test.js +1 -1
  256. package/tests/{plugins → jest/plugins}/tts/PageChunkIterator.test.js +3 -3
  257. package/tests/{plugins → jest/plugins}/tts/WebTTSEngine.test.js +47 -1
  258. package/tests/{plugins → jest/plugins}/tts/utils.test.js +1 -60
  259. package/tests/jest/plugins/url/UrlPlugin.test.js +198 -0
  260. package/tests/{plugins → jest/plugins/url}/plugin.url.test.js +53 -14
  261. package/tests/jest/setup.js +3 -0
  262. package/tests/{util → jest/util}/browserSniffing.test.js +1 -1
  263. package/tests/jest/util/docCookies.test.js +24 -0
  264. package/tests/{util → jest/util}/strings.test.js +1 -1
  265. package/tests/{utils.js → jest/utils.js} +38 -0
  266. package/webpack.config.js +12 -6
  267. package/.babelrc +0 -12
  268. package/.dependabot/config.yml +0 -6
  269. package/.testcaferc.json +0 -5
  270. package/BookReader/bookreader-component-bundle.js +0 -1450
  271. package/BookReader/bookreader-component-bundle.js.LICENSE.txt +0 -38
  272. package/BookReader/bookreader-component-bundle.js.map +0 -1
  273. package/BookReader/jquery-1.10.1.js +0 -2
  274. package/BookReader/jquery-1.10.1.js.LICENSE.txt +0 -24
  275. package/BookReader/plugins/plugin.menu_toggle.js +0 -2
  276. package/BookReader/plugins/plugin.menu_toggle.js.map +0 -1
  277. package/BookReader/plugins/plugin.mobile_nav.js +0 -2
  278. package/BookReader/plugins/plugin.mobile_nav.js.map +0 -1
  279. package/BookReaderDemo/IIIFBookReader.js +0 -207
  280. package/BookReaderDemo/bookreader-template-bundle.js +0 -7178
  281. package/BookReaderDemo/demo-iiif.js +0 -26
  282. package/BookReaderDemo/demo-plugin-menu-toggle.html +0 -34
  283. package/karma.conf.js +0 -23
  284. package/src/BookNavigator/BookModel.js +0 -14
  285. package/src/BookNavigator/BookNavigator.js +0 -446
  286. package/src/BookNavigator/assets/book-loader.js +0 -27
  287. package/src/BookNavigator/br-fullscreen-mgr.js +0 -83
  288. package/src/BookNavigator/search/a-search-result.js +0 -55
  289. package/src/BookReader/DebugConsole.js +0 -54
  290. package/src/BookReaderComponent/BookReaderComponent.js +0 -112
  291. package/src/ItemNavigator/ItemNavigator.js +0 -376
  292. package/src/ItemNavigator/providers/sharing.js +0 -29
  293. package/src/css/_MobileNav.scss +0 -194
  294. package/src/dragscrollable-br.js +0 -261
  295. package/src/lit-wrapper.js +0 -2
  296. package/src/plugins/menu_toggle/plugin.menu_toggle.js +0 -324
  297. package/src/plugins/plugin.mobile_nav.js +0 -287
  298. package/tests/BookReader/BookReaderPublicFunctions.test.js +0 -171
  299. package/tests/BookReader/DebugConsole.test.js +0 -25
  300. package/tests/BookReader/Mode1Up.test.js +0 -164
  301. package/tests/BookReader/Mode2Up.test.js +0 -247
  302. package/tests/BookReader/utils.test.js +0 -109
  303. package/tests/e2e/helpers/desktopSearch.js +0 -72
  304. package/tests/e2e/helpers/mobileSearch.js +0 -85
  305. package/tests/e2e/ia-production/ia-prod-base.js +0 -17
  306. package/tests/karma/BookNavigator/book-navigator.test.js +0 -132
  307. package/tests/karma/BookNavigator/search/search-provider.test.js +0 -23
  308. package/tests/karma/BookNavigator/visual-adjustments.test.js +0 -201
  309. package/tests/plugins/menu_toggle/plugin.menu_toggle.test.js +0 -68
  310. package/tests/plugins/plugin.chapters.test.js +0 -130
  311. package/tests/plugins/plugin.mobile_nav.test.js +0 -66
  312. package/tests/plugins/plugin.text_selection.test.js +0 -203
  313. package/tests/util/docCookies.test.js +0 -15
@@ -0,0 +1,776 @@
1
+ // @ts-check
2
+ import { customElement, property, query } from 'lit/decorators.js';
3
+ import {LitElement, html} from 'lit';
4
+ import { styleMap } from 'lit/directives/style-map.js';
5
+ import { ModeSmoothZoom } from './ModeSmoothZoom';
6
+ import { arrChanged, promisifyEvent } from './utils';
7
+ import { HTMLDimensionsCacher } from "./utils/HTMLDimensionsCacher";
8
+ import { PageModel } from './BookModel';
9
+ import { ModeCoordinateSpace } from './ModeCoordinateSpace';
10
+ /** @typedef {import('./BookModel').BookModel} BookModel */
11
+ /** @typedef {import('./BookModel').PageIndex} PageIndex */
12
+ /** @typedef {import('./ModeSmoothZoom').SmoothZoomable} SmoothZoomable */
13
+ /** @typedef {import('./PageContainer').PageContainer} PageContainer */
14
+ /** @typedef {import('../BookReader').default} BookReader */
15
+
16
+ // I _have_ to make this globally public, otherwise it won't let me call
17
+ // its constructor :/
18
+ /** @implements {SmoothZoomable} */
19
+ @customElement('br-mode-2up')
20
+ export class Mode2UpLit extends LitElement {
21
+ /****************************************/
22
+ /************** PROPERTIES **************/
23
+ /****************************************/
24
+
25
+ /** @type {BookReader} */
26
+ br;
27
+
28
+ /************** BOOK-RELATED PROPERTIES **************/
29
+
30
+ /** @type {BookModel} */
31
+ @property({ type: Object })
32
+ book;
33
+
34
+ /************** SCALE-RELATED PROPERTIES **************/
35
+
36
+ /** @type {ModeCoordinateSpace} Manage conversion between coordinates */
37
+ coordSpace = new ModeCoordinateSpace(this);
38
+
39
+ @property({ type: Number })
40
+ scale = 1;
41
+
42
+ initialScale = 1;
43
+
44
+ /** @type {import('./options').AutoFitValues} */
45
+ @property({ type: String })
46
+ autoFit = 'auto';
47
+
48
+ /************** VIRTUAL-FLIPPING PROPERTIES **************/
49
+
50
+ /** ms for flip animation */
51
+ flipSpeed = 400;
52
+
53
+ @query('.br-mode-2up__leafs--flipping') $flippingEdges;
54
+
55
+ /** @type {PageModel[]} */
56
+ @property({ type: Array, hasChanged: arrChanged })
57
+ visiblePages = [];
58
+
59
+ /** @type {PageModel | null} */
60
+ get pageLeft() {
61
+ return this.visiblePages.find(p => p.pageSide == 'L');
62
+ }
63
+
64
+ /** @type {PageModel | null} */
65
+ get pageRight() {
66
+ return this.visiblePages.find(p => p.pageSide == 'R');
67
+ }
68
+
69
+ /** @type {PageModel[]} */
70
+ @property({ type: Array })
71
+ renderedPages = [];
72
+
73
+ /** @type {Record<PageIndex, PageContainer>} position in inches */
74
+ pageContainerCache = {};
75
+
76
+ /** @type {{ direction: 'left' | 'right', pagesFlipping: [PageIndex, PageIndex], pagesFlippingCount: number }} */
77
+ activeFlip = null;
78
+
79
+ /** @private cache this value */
80
+ _leftCoverWidth = 0;
81
+
82
+ /************** DOM-RELATED PROPERTIES **************/
83
+
84
+ /** @type {HTMLElement} */
85
+ get $container() { return this; }
86
+
87
+ /** @type {HTMLElement} */
88
+ get $visibleWorld() { return this.$book; }
89
+
90
+ /** @type {HTMLElement} */
91
+ @query('.br-mode-2up__book')
92
+ $book;
93
+
94
+ get positions() {
95
+ return this.computePositions(this.pageLeft, this.pageRight);
96
+ }
97
+
98
+ /** @param {PageModel} page */
99
+ computePageHeight(page) {
100
+ return this.book.getMedianPageSizeInches().height;
101
+ }
102
+
103
+ /** @param {PageModel} page */
104
+ computePageWidth(page) {
105
+ return page.widthInches * this.computePageHeight(page) / page.heightInches;
106
+ }
107
+
108
+ /**
109
+ * @param {PageModel | null} pageLeft
110
+ * @param {PageModel | null} pageRight
111
+ */
112
+ computePositions(pageLeft, pageRight) {
113
+ const computePageWidth = this.computePageWidth.bind(this);
114
+ const numLeafs = this.book.getNumLeafs();
115
+ const movingPagesWidth = this.activeFlip ? Math.ceil(this.activeFlip.pagesFlippingCount / 2) * this.PAGE_THICKNESS_IN : 0;
116
+ const leftPagesCount = this.book.pageProgression == 'lr' ? (pageLeft?.index ?? 0) : (!pageLeft ? 0 : numLeafs - pageLeft.index);
117
+
118
+ // Everything is relative to the gutter
119
+ const gutter = this._leftCoverWidth + leftPagesCount * this.PAGE_THICKNESS_IN;
120
+
121
+ const pageLeftEnd = gutter;
122
+ const pageLeftWidth = !pageLeft ? computePageWidth(pageRight.right) : computePageWidth(pageLeft);
123
+ const pageLeftStart = gutter - pageLeftWidth;
124
+
125
+ const leafEdgesLeftEnd = pageLeftStart; // leafEdgesLeftStart + leafEdgesLeftMainWidth + leafEdgesLeftMovingWidth;
126
+ const leafEdgesLeftMovingWidth = this.activeFlip?.direction != 'left' ? 0 : movingPagesWidth;
127
+ const leafEdgesLeftMainWidth = Math.ceil(leftPagesCount / 2) * this.PAGE_THICKNESS_IN - leafEdgesLeftMovingWidth;
128
+ const leafEdgesLeftFullWidth = leafEdgesLeftMovingWidth + leafEdgesLeftMainWidth;
129
+ const leafEdgesLeftMovingStart = leafEdgesLeftEnd - leafEdgesLeftMovingWidth;
130
+ const leafEdgesLeftStart = leafEdgesLeftMovingStart - leafEdgesLeftMainWidth;
131
+
132
+ const pageRightStart = gutter;
133
+ const pageRightWidth = !pageRight ? 0 : computePageWidth(pageRight);
134
+ const pageRightEnd = pageRightStart + pageRightWidth;
135
+
136
+ const rightPagesCount = this.book.pageProgression == 'lr' ? (!pageRight ? 0 : numLeafs - pageRight.index) : (pageRight?.index ?? 0);
137
+ const leafEdgesRightStart = pageRightEnd;
138
+ const leafEdgesRightMovingWidth = this.activeFlip?.direction != 'right' ? 0 : movingPagesWidth;
139
+ const leafEdgesRightMainStart = leafEdgesRightStart + leafEdgesRightMovingWidth;
140
+ const leafEdgesRightMainWidth = Math.ceil(rightPagesCount / 2) * this.PAGE_THICKNESS_IN - leafEdgesRightMovingWidth;
141
+ const leafEdgesRightEnd = leafEdgesRightStart + leafEdgesRightMainWidth + leafEdgesRightMovingWidth;
142
+ const leafEdgesRightFullWidth = leafEdgesRightMovingWidth + leafEdgesRightMainWidth;
143
+
144
+ const spreadWidth = pageRightEnd - pageLeftStart;
145
+ const bookWidth = leafEdgesRightEnd - leafEdgesLeftStart;
146
+ return {
147
+ leafEdgesLeftStart,
148
+ leafEdgesLeftMainWidth,
149
+ leafEdgesLeftMovingStart,
150
+ leafEdgesLeftMovingWidth,
151
+ leafEdgesLeftEnd,
152
+ leafEdgesLeftFullWidth,
153
+
154
+ pageLeftStart,
155
+ pageLeftWidth,
156
+ pageLeftEnd,
157
+
158
+ gutter,
159
+
160
+ pageRightStart,
161
+ pageRightWidth,
162
+ pageRightEnd,
163
+
164
+ leafEdgesRightStart,
165
+ leafEdgesRightMovingWidth,
166
+ leafEdgesRightMainStart,
167
+ leafEdgesRightMainWidth,
168
+ leafEdgesRightEnd,
169
+ leafEdgesRightFullWidth,
170
+
171
+ spreadWidth,
172
+ bookWidth,
173
+ };
174
+ }
175
+
176
+ /** @type {HTMLDimensionsCacher} Cache things like clientWidth to reduce repaints */
177
+ htmlDimensionsCacher = new HTMLDimensionsCacher(this);
178
+
179
+ smoothZoomer = new ModeSmoothZoom(this);
180
+
181
+ /************** CONSTANT PROPERTIES **************/
182
+
183
+ /** How much to zoom when zoom button pressed */
184
+ ZOOM_FACTOR = 1.1;
185
+
186
+ /** How thick a page is in the real world, as an estimate for the leafs */
187
+ PAGE_THICKNESS_IN = 0.002;
188
+
189
+ /****************************************/
190
+ /************** PUBLIC API **************/
191
+ /****************************************/
192
+
193
+ /************** MAIN PUBLIC METHODS **************/
194
+
195
+ /**
196
+ * @param {PageIndex} index
197
+ * TODO Remove smooth option from everywhere.
198
+ */
199
+ async jumpToIndex(index, { smooth = true } = {}) {
200
+ await this.flipAnimation(index, { animate: smooth });
201
+ }
202
+
203
+ zoomIn() {
204
+ this.scale *= this.ZOOM_FACTOR;
205
+ }
206
+
207
+ zoomOut() {
208
+ this.scale *= 1 / this.ZOOM_FACTOR;
209
+ }
210
+
211
+ /********************************************/
212
+ /************** INTERNAL STUFF **************/
213
+ /********************************************/
214
+
215
+ /************** LIFE CYCLE **************/
216
+
217
+ /**
218
+ * @param {BookModel} book
219
+ * @param {BookReader} br
220
+ */
221
+ constructor(book, br) {
222
+ super();
223
+ this.book = book;
224
+
225
+ /** @type {BookReader} */
226
+ this.br = br;
227
+ }
228
+
229
+ /** @override */
230
+ firstUpdated(changedProps) {
231
+ super.firstUpdated(changedProps);
232
+ this.htmlDimensionsCacher.updateClientSizes();
233
+ this.smoothZoomer.attach();
234
+ }
235
+
236
+ /** @override */
237
+ connectedCallback() {
238
+ super.connectedCallback();
239
+ this.htmlDimensionsCacher.attachResizeListener();
240
+ this.smoothZoomer.attach();
241
+ }
242
+
243
+ /** @override */
244
+ disconnectedCallback() {
245
+ this.htmlDimensionsCacher.detachResizeListener();
246
+ this.smoothZoomer.detach();
247
+ super.disconnectedCallback();
248
+ }
249
+
250
+ /** @override */
251
+ updated(changedProps) {
252
+ // this.X is the new value
253
+ // changedProps.get('X') is the old value
254
+ if (changedProps.has('book')) {
255
+ this._leftCoverWidth = this.computePageWidth(this.book.getPage(this.book.pageProgression == 'lr' ? 0 : -1));
256
+ }
257
+ if (changedProps.has('visiblePages')) {
258
+ this.renderedPages = this.computeRenderedPages();
259
+ this.br.displayedIndices = this.visiblePages.map(p => p.index);
260
+ this.br.updateFirstIndex(this.br.displayedIndices[0]);
261
+ this.br._components.navbar.updateNavIndexThrottled();
262
+ }
263
+ if (changedProps.has('autoFit')) {
264
+ if (this.autoFit != 'none') {
265
+ this.resizeViaAutofit();
266
+ }
267
+ }
268
+ if (changedProps.has('scale')) {
269
+ const oldVal = changedProps.get('scale');
270
+ this.recenter();
271
+ this.smoothZoomer.updateViewportOnZoom(this.scale, oldVal);
272
+ }
273
+ }
274
+
275
+ /************** LIT CONFIGS **************/
276
+
277
+ /** @override */
278
+ createRenderRoot() {
279
+ // Disable shadow DOM; that would require a huge rejiggering of CSS
280
+ return this;
281
+ }
282
+
283
+ /************** RENDERING **************/
284
+
285
+ /** @override */
286
+ render() {
287
+ return html`
288
+ <div class="br-mode-2up__book" @mouseup=${this.handlePageClick}>
289
+ ${this.renderLeafEdges('left')}
290
+ ${this.renderedPages.map(p => this.renderPage(p))}
291
+ ${this.renderLeafEdges('right')}
292
+ </div>`;
293
+ }
294
+
295
+ /** @param {PageModel} page */
296
+ createPageContainer = (page) => {
297
+ return this.pageContainerCache[page.index] || (
298
+ this.pageContainerCache[page.index] = (
299
+ // @ts-ignore I know it's protected, TS! But Mode2Up and BookReader are friends.
300
+ this.br._createPageContainer(page.index)
301
+ )
302
+ );
303
+ }
304
+
305
+ /**
306
+ * @param {PageIndex} startIndex
307
+ */
308
+ initFirstRender(startIndex) {
309
+ const page = this.book.getPage(startIndex);
310
+ const spread = page.spread;
311
+ this.visiblePages = (
312
+ this.book.pageProgression == 'lr' ? [spread.left, spread.right] : [spread.right, spread.left]
313
+ ).filter(p => p);
314
+ this.htmlDimensionsCacher.updateClientSizes();
315
+ this.resizeViaAutofit(page);
316
+ this.initialScale = this.scale;
317
+ }
318
+
319
+ /** @param {PageModel} page */
320
+ renderPage = (page) => {
321
+ const wToR = this.coordSpace.worldUnitsToRenderedPixels;
322
+ const wToV = this.coordSpace.worldUnitsToVisiblePixels;
323
+
324
+ const width = wToR(this.computePageWidth(page));
325
+ const height = wToR(this.computePageHeight(page));
326
+ const isVisible = this.visiblePages.map(p => p.index).includes(page.index);
327
+ const positions = this.computePositions(page.spread.left, page.spread.right);
328
+
329
+ const pageContainerEl = this.createPageContainer(page)
330
+ .update({
331
+ dimensions: {
332
+ width,
333
+ height,
334
+ top: 0,
335
+ left: wToR(page.pageSide == 'L' ? positions.pageLeftStart : positions.pageLeftEnd),
336
+ },
337
+ reduce: page.width / wToV(this.computePageWidth(page)),
338
+ }).$container[0];
339
+
340
+ pageContainerEl.classList.toggle('BRpage-visible', isVisible);
341
+ return pageContainerEl;
342
+ }
343
+
344
+ /**
345
+ * @param {'left' | 'right'} side
346
+ * Renders the current leaf edges, as well as any "moving" leaf edges,
347
+ * i.e. leaf edges that are currently being flipped. Uses a custom element
348
+ * to render br-leaf-edges.
349
+ **/
350
+ renderLeafEdges = (side) => {
351
+ if (!this.visiblePages.length) return html``;
352
+ const fullWidthIn = side == 'left' ? this.positions.leafEdgesLeftFullWidth : this.positions.leafEdgesRightFullWidth;
353
+ if (!fullWidthIn) return html``;
354
+
355
+ const wToR = this.coordSpace.worldUnitsToRenderedPixels;
356
+ const height = wToR(this.computePageHeight(this.visiblePages[0]));
357
+ const hasMovingPages = this.activeFlip?.direction == side;
358
+
359
+ const leftmostPage = this.book.getPage(this.book.pageProgression == 'lr' ? 0 : this.book.getNumLeafs() - 1);
360
+ const rightmostPage = this.book.getPage(this.book.pageProgression == 'lr' ? this.book.getNumLeafs() - 1 : 0);
361
+ const numPagesFlipping = hasMovingPages ? this.activeFlip.pagesFlippingCount : 0;
362
+ const range = side == 'left' ?
363
+ [leftmostPage.index, this.pageLeft.goLeft(numPagesFlipping).index] :
364
+ [this.pageRight.goRight(numPagesFlipping).index, rightmostPage.index];
365
+
366
+ const mainEdgesStyle = {
367
+ width: `${wToR(side == 'left' ? this.positions.leafEdgesLeftMainWidth : this.positions.leafEdgesRightMainWidth)}px`,
368
+ height: `${height}px`,
369
+ left: `${wToR(side == 'left' ? this.positions.leafEdgesLeftStart : this.positions.leafEdgesRightMainStart)}px`,
370
+ };
371
+ const mainEdges = html`
372
+ <br-leaf-edges
373
+ leftIndex=${range[0]}
374
+ rightIndex=${range[1]}
375
+ .book=${this.book}
376
+ .pageClickHandler=${(index) => this.br.jumpToIndex(index)}
377
+ side=${side}
378
+ class="br-mode-2up__leafs br-mode-2up__leafs--${side}"
379
+ style=${styleMap(mainEdgesStyle)}
380
+ ></br-leaf-edges>
381
+ `;
382
+
383
+ if (hasMovingPages) {
384
+ const width = wToR(side == 'left' ? this.positions.leafEdgesLeftMovingWidth : this.positions.leafEdgesRightMovingWidth);
385
+ const style = {
386
+ width: `${width}px`,
387
+ height: `${height}px`,
388
+ left: `${wToR(side == 'left' ? this.positions.leafEdgesLeftMovingStart : this.positions.leafEdgesRightStart)}px`,
389
+ pointerEvents: 'none',
390
+ transformOrigin: `${wToR(side == 'left' ? this.positions.pageLeftWidth : -this.positions.pageRightWidth) + width / 2}px 0`,
391
+ };
392
+
393
+ const movingEdges = html`
394
+ <br-leaf-edges
395
+ leftIndex=${this.activeFlip.pagesFlipping[0]}
396
+ rightIndex=${this.activeFlip.pagesFlipping[1]}
397
+ .book=${this.book}
398
+ side=${side}
399
+ class="br-mode-2up__leafs br-mode-2up__leafs--${side} br-mode-2up__leafs--flipping"
400
+ style=${styleMap(style)}
401
+ ></br-leaf-edges>
402
+ `;
403
+
404
+ return side == 'left' ? html`${mainEdges}${movingEdges}` : html`${movingEdges}${mainEdges}`;
405
+ } else {
406
+ return mainEdges;
407
+ }
408
+ }
409
+
410
+ resizeViaAutofit(page = this.visiblePages[0]) {
411
+ this.scale = this.computeScale(page, this.autoFit);
412
+ }
413
+
414
+ recenter(page = this.visiblePages[0]) {
415
+ const translate = this.computeTranslate(page, this.scale);
416
+ this.$book.style.transform = `translateX(${translate.x}px) translateY(${translate.y}px) scale(${this.scale})`;
417
+ }
418
+
419
+ /**
420
+ * @returns {PageModel[]}
421
+ */
422
+ computeRenderedPages() {
423
+ // Also render 2 pages before/after
424
+ // @ts-ignore TS doesn't understand the filtering out of null values
425
+ return [
426
+ this.visiblePages[0]?.prev?.prev,
427
+ this.visiblePages[0]?.prev,
428
+ ...this.visiblePages,
429
+ this.visiblePages[this.visiblePages.length - 1]?.next,
430
+ this.visiblePages[this.visiblePages.length - 1]?.next?.next,
431
+ ]
432
+ .filter(p => p)
433
+ // Never render more than 10 pages! Usually means something is wrong
434
+ .slice(0, 10);
435
+ }
436
+
437
+ /**
438
+ * @param {PageModel} page
439
+ * @param {import('./options').AutoFitValues} autoFit
440
+ */
441
+ computeScale(page, autoFit) {
442
+ if (!page) return 1;
443
+ const spread = page.spread;
444
+ const bookWidth = this.computePositions(spread.left, spread.right).bookWidth;
445
+ const bookHeight = this.computePageHeight(spread.left || spread.right);
446
+ const BOOK_PADDING_PX = 10;
447
+ const curScale = this.scale;
448
+ this.scale = 1; // Need this temporarily
449
+ const widthScale = this.coordSpace.renderedPixelsToWorldUnits(this.htmlDimensionsCacher.clientWidth - 2 * BOOK_PADDING_PX) / bookWidth;
450
+ const heightScale = this.coordSpace.renderedPixelsToWorldUnits(this.htmlDimensionsCacher.clientHeight - 2 * BOOK_PADDING_PX) / bookHeight;
451
+ this.scale = curScale;
452
+ const realScale = 1;
453
+
454
+ let scale = realScale;
455
+ if (autoFit == 'width') {
456
+ scale = widthScale;
457
+ } else if (autoFit == 'height') {
458
+ scale = heightScale;
459
+ } else if (autoFit == 'auto') {
460
+ scale = Math.min(widthScale, heightScale);
461
+ } else if (autoFit == 'none') {
462
+ scale = this.scale;
463
+ } else {
464
+ // Should be impossible
465
+ throw new Error(`Invalid autoFit value: ${autoFit}`);
466
+ }
467
+
468
+ return scale;
469
+ }
470
+
471
+ /**
472
+ * @param {PageModel} page
473
+ * @param {number} scale
474
+ * @returns {{x: number, y: number}}
475
+ */
476
+ computeTranslate(page, scale = this.scale) {
477
+ if (!page) return { x: 0, y: 0 };
478
+ const spread = page.spread;
479
+ // Default to real size if it fits, otherwise default to full height
480
+ const positions = this.computePositions(spread.left, spread.right);
481
+ const bookWidth = positions.bookWidth;
482
+ const bookHeight = this.computePageHeight(spread.left || spread.right);
483
+ const visibleBookWidth = this.coordSpace.worldUnitsToRenderedPixels(bookWidth) * scale;
484
+ const visibleBookHeight = this.coordSpace.worldUnitsToRenderedPixels(bookHeight) * scale;
485
+ const leftOffset = this.coordSpace.worldUnitsToRenderedPixels(-positions.leafEdgesLeftStart) * scale;
486
+ const translateX = (this.htmlDimensionsCacher.clientWidth - visibleBookWidth) / 2 + leftOffset;
487
+ const translateY = (this.htmlDimensionsCacher.clientHeight - visibleBookHeight) / 2;
488
+ return { x: Math.max(leftOffset, translateX), y: Math.max(0, translateY) };
489
+ }
490
+
491
+ /************** VIRTUAL FLIPPING LOGIC **************/
492
+
493
+ /**
494
+ * @param {'left' | 'right' | 'next' | 'prev' | PageIndex | PageModel | {left: PageModel | null, right: PageModel | null}} nextSpread
495
+ */
496
+ async flipAnimation(nextSpread, { animate = true } = {}) {
497
+ const curSpread = (this.pageLeft || this.pageRight)?.spread;
498
+ if (!curSpread) {
499
+ // Nothings been actually rendered yet! Will be corrected during initFirstRender
500
+ return;
501
+ }
502
+
503
+ nextSpread = this.parseNextSpread(nextSpread);
504
+ if (this.activeFlip || !nextSpread) return;
505
+
506
+ const progression = this.book.pageProgression;
507
+ const curLeftIndex = curSpread.left?.index ?? (progression == 'lr' ? -1 : this.book.getNumLeafs());
508
+ const nextLeftIndex = nextSpread.left?.index ?? (progression == 'lr' ? -1 : this.book.getNumLeafs());
509
+ if (curLeftIndex == nextLeftIndex) return;
510
+
511
+ const renderedIndices = this.renderedPages.map(p => p.index);
512
+ /** @type {PageContainer[]} */
513
+ const nextPageContainers = [];
514
+ for (const page of [nextSpread.left, nextSpread.right]) {
515
+ if (!page) continue;
516
+ nextPageContainers.push(this.createPageContainer(page));
517
+ if (!renderedIndices.includes(page.index)) {
518
+ this.renderedPages.push(page);
519
+ }
520
+ }
521
+
522
+ const curTranslate = this.computeTranslate(curSpread.left || curSpread.right, this.scale);
523
+ const idealNextTranslate = this.computeTranslate(nextSpread.left || nextSpread.right, this.scale);
524
+ const translateDiff = Math.sqrt((idealNextTranslate.x - curTranslate.x) ** 2 + (idealNextTranslate.y - curTranslate.y) ** 2);
525
+ let nextTranslate = `translate(${idealNextTranslate.x}px, ${idealNextTranslate.y}px)`;
526
+ if (translateDiff < 50) {
527
+ const activeTranslate = this.$book.style.transform.match(/translate\([^)]+\)/)?.[0];
528
+ if (activeTranslate) {
529
+ nextTranslate = activeTranslate;
530
+ }
531
+ }
532
+ const newTransform = `${nextTranslate} scale(${this.scale})`;
533
+
534
+ if (animate && 'animate' in Element.prototype) {
535
+ // This table is used to determine the direction of the flip animation:
536
+ // | < | >
537
+ // lr | L | R
538
+ // rl | R | L
539
+ const direction = progression == 'lr' ? (nextLeftIndex > curLeftIndex ? 'right' : 'left') : (nextLeftIndex > curLeftIndex ? 'left' : 'right');
540
+
541
+ this.activeFlip = {
542
+ direction,
543
+ pagesFlipping: [curLeftIndex, nextLeftIndex],
544
+ pagesFlippingCount: Math.abs(nextLeftIndex - curLeftIndex),
545
+ };
546
+
547
+ this.classList.add(`br-mode-2up--flipping-${direction}`);
548
+ this.classList.add(`BRpageFlipping`);
549
+
550
+ // Wait for lit update cycle to finish so that entering pages are rendered
551
+ this.requestUpdate();
552
+ await this.updateComplete;
553
+
554
+ this.visiblePages
555
+ .map(p => this.pageContainerCache[p.index].$container)
556
+ .forEach($c => $c.addClass('BRpage-exiting'));
557
+
558
+ nextPageContainers.forEach(c => c.$container.addClass('BRpage-entering'));
559
+
560
+ /** @type {KeyframeAnimationOptions} */
561
+ const animationStyle = {
562
+ duration: this.flipSpeed + this.activeFlip.pagesFlippingCount,
563
+ easing: 'ease-in',
564
+ fill: 'none',
565
+ };
566
+
567
+ const bookCenteringAnimation = this.$book.animate([
568
+ { transform: newTransform },
569
+ ], animationStyle);
570
+
571
+ const edgeTranslationAnimation = this.$flippingEdges.animate([
572
+ { transform: `rotateY(0deg)` },
573
+ {
574
+ transform: direction == 'left' ? `rotateY(-180deg)` : `rotateY(180deg)`,
575
+ },
576
+ ], animationStyle);
577
+
578
+ const exitingPageAnimation = direction == 'left' ?
579
+ this.querySelector('.BRpage-exiting[data-side=L]').animate([
580
+ { transform: `rotateY(0deg)` },
581
+ { transform: `rotateY(180deg)` },
582
+ ], animationStyle) : this.querySelector('.BRpage-exiting[data-side=R]').animate([
583
+ { transform: `rotateY(0deg)` },
584
+ { transform: `rotateY(-180deg)` },
585
+ ], animationStyle);
586
+
587
+ const enteringPageAnimation = direction == 'left' ?
588
+ this.querySelector('.BRpage-entering[data-side=R]').animate([
589
+ { transform: `rotateY(-180deg)` },
590
+ { transform: `rotateY(0deg)` },
591
+ ], animationStyle) : this.querySelector('.BRpage-entering[data-side=L]').animate([
592
+ { transform: `rotateY(180deg)` },
593
+ { transform: `rotateY(0deg)` },
594
+ ], animationStyle);
595
+
596
+ bookCenteringAnimation.play();
597
+ edgeTranslationAnimation.play();
598
+ exitingPageAnimation.play();
599
+ enteringPageAnimation.play();
600
+
601
+ nextPageContainers.forEach(c => c.$container.addClass('BRpage-visible'));
602
+
603
+ await Promise.race([
604
+ promisifyEvent(bookCenteringAnimation, 'finish'),
605
+ promisifyEvent(edgeTranslationAnimation, 'finish'),
606
+ promisifyEvent(exitingPageAnimation, 'finish'),
607
+ promisifyEvent(enteringPageAnimation, 'finish'),
608
+ ]);
609
+
610
+ this.classList.remove(`br-mode-2up--flipping-${direction}`);
611
+ this.classList.remove(`BRpageFlipping`);
612
+
613
+ this.visiblePages
614
+ .map(p => this.pageContainerCache[p.index].$container)
615
+ .forEach($c => $c.removeClass('BRpage-exiting BRpage-visible'));
616
+ nextPageContainers.forEach(c => c.$container.removeClass('BRpage-entering'));
617
+ this.activeFlip = null;
618
+ }
619
+
620
+ this.$book.style.transform = newTransform;
621
+ this.visiblePages = (
622
+ progression == 'lr' ? [nextSpread.left, nextSpread.right] : [nextSpread.right, nextSpread.left]
623
+ ).filter(x => x);
624
+ }
625
+
626
+ /**
627
+ * @param {'left' | 'right' | 'next' | 'prev' | PageIndex | PageModel | {left: PageModel | null, right: PageModel | null}} nextSpread
628
+ * @returns {{left: PageModel | null, right: PageModel | null}}
629
+ */
630
+ parseNextSpread(nextSpread) {
631
+ if (nextSpread == 'next') {
632
+ nextSpread = this.book.pageProgression == 'lr' ? 'right' : 'left';
633
+ } else if (nextSpread == 'prev') {
634
+ nextSpread = this.book.pageProgression == 'lr' ? 'left' : 'right';
635
+ }
636
+
637
+ const curSpread = (this.pageLeft || this.pageRight).spread;
638
+
639
+ if (nextSpread == 'left') {
640
+ nextSpread = curSpread.left?.findLeft({ combineConsecutiveUnviewables: true })?.spread;
641
+ } else if (nextSpread == 'right') {
642
+ nextSpread = curSpread.right?.findRight({ combineConsecutiveUnviewables: true })?.spread;
643
+ }
644
+
645
+ if (typeof(nextSpread) == 'number') {
646
+ nextSpread = this.book.getPage(nextSpread).spread;
647
+ }
648
+
649
+ if (nextSpread instanceof PageModel) {
650
+ nextSpread = nextSpread.spread;
651
+ }
652
+
653
+ return nextSpread;
654
+ }
655
+
656
+ /************** INPUT HANDLERS **************/
657
+
658
+ /**
659
+ * @param {MouseEvent} ev
660
+ */
661
+ handlePageClick = (ev) => {
662
+ // right click
663
+ if (ev.which == 3 && this.br.protected) {
664
+ return false;
665
+ }
666
+
667
+ if (ev.which != 1) return;
668
+
669
+ const $page = $(ev.target).closest('.BRpagecontainer');
670
+ if (!$page.length) return;
671
+ if ($page.data('side') == 'L') {
672
+ this.flipAnimation('left');
673
+ } else if ($page.data('side') == 'R') {
674
+ this.flipAnimation('right');
675
+ }
676
+ }
677
+ }
678
+
679
+ @customElement('br-leaf-edges')
680
+ export class LeafEdges extends LitElement {
681
+ @property({ type: Number }) leftIndex = 0;
682
+ @property({ type: Number }) rightIndex = 0;
683
+ /** @type {'left' | 'right'} */
684
+ @property({ type: String }) side = 'left';
685
+
686
+ /** @type {BookModel} */
687
+ @property({attribute: false})
688
+ book = null;
689
+
690
+ /** @type {(index: PageIndex) => void} */
691
+ @property({attribute: false, type: Function})
692
+ pageClickHandler = null;
693
+
694
+ @query('.br-leaf-edges__bar') $hoverBar;
695
+ @query('.br-leaf-edges__label') $hoverLabel;
696
+
697
+ get pageWidthPercent() {
698
+ return 100 * 1 / (this.rightIndex - this.leftIndex + 1);
699
+ }
700
+
701
+ render() {
702
+ return html`
703
+ <div
704
+ class="br-leaf-edges__bar"
705
+ style="${styleMap({width: `${this.pageWidthPercent}%`})}"
706
+ ></div>
707
+ <div class="br-leaf-edges__label">Page</div>`;
708
+ }
709
+
710
+ connectedCallback() {
711
+ super.connectedCallback();
712
+ this.addEventListener('mouseenter', this.onMouseEnter);
713
+ this.addEventListener('mouseleave', this.onMouseLeave);
714
+ this.addEventListener('click', this.onClick);
715
+ }
716
+ disconnectedCallback() {
717
+ super.disconnectedCallback();
718
+ this.addEventListener('mouseenter', this.onMouseEnter);
719
+ this.removeEventListener('mousemove', this.onMouseMove);
720
+ this.removeEventListener('mouseleave', this.onMouseLeave);
721
+ }
722
+
723
+ /** @override */
724
+ createRenderRoot() {
725
+ // Disable shadow DOM; that would require a huge rejiggering of CSS
726
+ return this;
727
+ }
728
+
729
+ /**
730
+ * @param {MouseEvent} e
731
+ */
732
+ onMouseEnter = (e) => {
733
+ this.addEventListener('mousemove', this.onMouseMove);
734
+ this.$hoverBar.style.display = 'block';
735
+ this.$hoverLabel.style.display = 'block';
736
+ }
737
+
738
+ /**
739
+ * @param {MouseEvent} e
740
+ */
741
+ onMouseMove = (e) => {
742
+ this.$hoverBar.style.left = `${e.offsetX}px`;
743
+ if (this.side == 'right') {
744
+ this.$hoverLabel.style.left = `${e.offsetX}px`;
745
+ } else {
746
+ this.$hoverLabel.style.right = `${this.offsetWidth - e.offsetX}px`;
747
+ }
748
+ this.$hoverLabel.style.top = `${e.offsetY}px`;
749
+ const index = this.mouseEventToPageIndex(e);
750
+ this.$hoverLabel.textContent = this.book.getPageName(index);
751
+ }
752
+
753
+ /**
754
+ * @param {MouseEvent} e
755
+ */
756
+ onMouseLeave = (e) => {
757
+ this.removeEventListener('mousemove', this.onMouseMove);
758
+ this.$hoverBar.style.display = 'none';
759
+ this.$hoverLabel.style.display = 'none';
760
+ }
761
+
762
+ /**
763
+ * @param {MouseEvent} e
764
+ */
765
+ onClick = (e) => {
766
+ this.pageClickHandler(this.mouseEventToPageIndex(e));
767
+ }
768
+
769
+ /**
770
+ * @param {MouseEvent} e
771
+ * @returns {PageIndex}
772
+ */
773
+ mouseEventToPageIndex(e) {
774
+ return Math.floor(this.leftIndex + (e.offsetX / this.offsetWidth) * (this.rightIndex - this.leftIndex + 1));
775
+ }
776
+ }