@internetarchive/bookreader 5.0.0-28 → 5.0.0-30-a

Sign up to get free protection for your applications and to get access to all the features.
Files changed (233) 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/ia-bookreader-bundle.js +1458 -0
  6. package/BookReader/{bookreader-component-bundle.js.LICENSE.txt → ia-bookreader-bundle.js.LICENSE.txt} +12 -0
  7. package/BookReader/ia-bookreader-bundle.js.map +1 -0
  8. package/BookReader/plugins/plugin.search.js +1 -1
  9. package/BookReader/plugins/plugin.search.js.map +1 -1
  10. package/BookReader/plugins/plugin.url.js +1 -1
  11. package/BookReader/plugins/plugin.url.js.map +1 -1
  12. package/BookReaderDemo/BookReaderDemo.css +14 -1
  13. package/BookReaderDemo/IADemoBr.js +107 -0
  14. package/BookReaderDemo/demo-internetarchive.html +64 -99
  15. package/CHANGELOG.md +4 -0
  16. package/package.json +9 -6
  17. package/src/BookNavigator/assets/ia-logo.js +17 -0
  18. package/src/BookNavigator/book-navigator.js +528 -0
  19. package/src/BookNavigator/bookmarks/bookmark-button.js +2 -1
  20. package/src/BookNavigator/bookmarks/bookmarks-provider.js +20 -8
  21. package/src/BookNavigator/bookmarks/ia-bookmarks.js +84 -51
  22. package/src/BookNavigator/downloads/downloads-provider.js +5 -9
  23. package/src/BookNavigator/downloads/downloads.js +1 -0
  24. package/src/BookNavigator/search/search-provider.js +15 -8
  25. package/src/BookNavigator/sharing.js +27 -0
  26. package/src/BookNavigator/visual-adjustments/visual-adjustments-provider.js +9 -8
  27. package/src/BookNavigator/volumes/volumes-provider.js +3 -4
  28. package/src/BookReader/options.js +6 -0
  29. package/src/BookReader.js +20 -8
  30. package/src/css/_BRComponent.scss +1 -1
  31. package/src/ia-bookreader/ia-bookreader.js +205 -0
  32. package/src/plugins/search/plugin.search.js +9 -9
  33. package/src/plugins/url/UrlPlugin.js +5 -6
  34. package/{src → stat}/BookNavigator/BookModel.js +0 -0
  35. package/{src → stat}/BookNavigator/BookNavigator.js +109 -102
  36. package/stat/BookNavigator/assets/bookmark-colors.js +15 -0
  37. package/stat/BookNavigator/assets/button-base.js +61 -0
  38. package/stat/BookNavigator/assets/ia-logo.js +17 -0
  39. package/stat/BookNavigator/assets/icon_checkmark.js +6 -0
  40. package/stat/BookNavigator/assets/icon_close.js +3 -0
  41. package/stat/BookNavigator/assets/icon_sort_asc.js +5 -0
  42. package/stat/BookNavigator/assets/icon_sort_desc.js +5 -0
  43. package/stat/BookNavigator/assets/icon_sort_neutral.js +5 -0
  44. package/stat/BookNavigator/assets/icon_volumes.js +11 -0
  45. package/stat/BookNavigator/bookmarks/bookmark-button.js +64 -0
  46. package/stat/BookNavigator/bookmarks/bookmark-edit.js +215 -0
  47. package/stat/BookNavigator/bookmarks/bookmarks-list.js +285 -0
  48. package/stat/BookNavigator/bookmarks/bookmarks-loginCTA.js +28 -0
  49. package/stat/BookNavigator/bookmarks/bookmarks-provider.js +56 -0
  50. package/stat/BookNavigator/bookmarks/ia-bookmarks.js +523 -0
  51. package/{src → stat}/BookNavigator/br-fullscreen-mgr.js +1 -2
  52. package/stat/BookNavigator/delete-modal-actions.js +49 -0
  53. package/stat/BookNavigator/downloads/downloads-provider.js +72 -0
  54. package/stat/BookNavigator/downloads/downloads.js +139 -0
  55. package/stat/BookNavigator/provider-config.js +0 -0
  56. package/stat/BookNavigator/search/a-search-result.js +55 -0
  57. package/stat/BookNavigator/search/search-provider.js +180 -0
  58. package/stat/BookNavigator/search/search-results.js +360 -0
  59. package/{src/ItemNavigator/providers → stat/BookNavigator}/sharing.js +3 -5
  60. package/stat/BookNavigator/visual-adjustments/visual-adjustments-provider.js +94 -0
  61. package/stat/BookNavigator/visual-adjustments/visual-adjustments.js +280 -0
  62. package/stat/BookNavigator/volumes/volumes-provider.js +83 -0
  63. package/stat/BookNavigator/volumes/volumes.js +178 -0
  64. package/stat/BookReader/BookModel.js +518 -0
  65. package/stat/BookReader/DebugConsole.js +54 -0
  66. package/stat/BookReader/DragScrollable.js +233 -0
  67. package/stat/BookReader/ImageCache.js +116 -0
  68. package/stat/BookReader/Mode1Up.js +102 -0
  69. package/stat/BookReader/Mode1UpLit.js +434 -0
  70. package/stat/BookReader/Mode2Up.js +1372 -0
  71. package/stat/BookReader/ModeSmoothZoom.js +177 -0
  72. package/stat/BookReader/ModeThumb.js +344 -0
  73. package/stat/BookReader/Navbar/Navbar.js +310 -0
  74. package/stat/BookReader/PageContainer.js +120 -0
  75. package/stat/BookReader/ReduceSet.js +26 -0
  76. package/stat/BookReader/Toolbar/Toolbar.js +384 -0
  77. package/stat/BookReader/events.js +20 -0
  78. package/stat/BookReader/options.js +324 -0
  79. package/stat/BookReader/utils/HTMLDimensionsCacher.js +44 -0
  80. package/stat/BookReader/utils/classes.js +36 -0
  81. package/stat/BookReader/utils.js +240 -0
  82. package/stat/BookReader.js +2550 -0
  83. package/{src → stat}/BookReaderComponent/BookReaderComponent.js +15 -10
  84. package/stat/assets/icons/1up.svg +12 -0
  85. package/stat/assets/icons/2up.svg +15 -0
  86. package/stat/assets/icons/advance.svg +26 -0
  87. package/stat/assets/icons/chevron-right.svg +1 -0
  88. package/stat/assets/icons/close-circle-dark.svg +1 -0
  89. package/stat/assets/icons/close-circle.svg +1 -0
  90. package/stat/assets/icons/fullscreen.svg +17 -0
  91. package/stat/assets/icons/fullscreen_exit.svg +17 -0
  92. package/stat/assets/icons/hamburger.svg +15 -0
  93. package/stat/assets/icons/left-arrow.svg +12 -0
  94. package/stat/assets/icons/magnify-minus.svg +16 -0
  95. package/stat/assets/icons/magnify-plus.svg +17 -0
  96. package/stat/assets/icons/magnify.svg +15 -0
  97. package/stat/assets/icons/pause.svg +23 -0
  98. package/stat/assets/icons/play.svg +22 -0
  99. package/stat/assets/icons/playback-speed.svg +34 -0
  100. package/stat/assets/icons/read-aloud.svg +22 -0
  101. package/stat/assets/icons/review.svg +22 -0
  102. package/stat/assets/icons/thumbnails.svg +17 -0
  103. package/stat/assets/icons/voice.svg +1 -0
  104. package/stat/assets/icons/volume-full.svg +22 -0
  105. package/stat/assets/images/BRicons.png +0 -0
  106. package/stat/assets/images/BRicons.svg +94 -0
  107. package/stat/assets/images/BRicons_ia.png +0 -0
  108. package/stat/assets/images/back_pages.png +0 -0
  109. package/stat/assets/images/book_bottom_icon.png +0 -0
  110. package/stat/assets/images/book_down_icon.png +0 -0
  111. package/stat/assets/images/book_left_icon.png +0 -0
  112. package/stat/assets/images/book_leftmost_icon.png +0 -0
  113. package/stat/assets/images/book_right_icon.png +0 -0
  114. package/stat/assets/images/book_rightmost_icon.png +0 -0
  115. package/stat/assets/images/book_top_icon.png +0 -0
  116. package/stat/assets/images/book_up_icon.png +0 -0
  117. package/stat/assets/images/books_graphic.svg +177 -0
  118. package/stat/assets/images/booksplit.png +0 -0
  119. package/stat/assets/images/control_pause_icon.png +0 -0
  120. package/stat/assets/images/control_play_icon.png +0 -0
  121. package/stat/assets/images/embed_icon.png +0 -0
  122. package/stat/assets/images/icon-home-ia.png +0 -0
  123. package/stat/assets/images/icon_OL-logo-xs.png +0 -0
  124. package/stat/assets/images/icon_alert-xs.png +0 -0
  125. package/stat/assets/images/icon_book.svg +12 -0
  126. package/stat/assets/images/icon_bookmark.svg +12 -0
  127. package/stat/assets/images/icon_close-pop.png +0 -0
  128. package/stat/assets/images/icon_download.png +0 -0
  129. package/stat/assets/images/icon_gear.svg +14 -0
  130. package/stat/assets/images/icon_hamburger.svg +20 -0
  131. package/stat/assets/images/icon_home.png +0 -0
  132. package/stat/assets/images/icon_home.svg +21 -0
  133. package/stat/assets/images/icon_home_ia.png +0 -0
  134. package/stat/assets/images/icon_indicator.png +0 -0
  135. package/stat/assets/images/icon_info.svg +11 -0
  136. package/stat/assets/images/icon_one_page.svg +8 -0
  137. package/stat/assets/images/icon_pause.svg +1 -0
  138. package/stat/assets/images/icon_play.svg +1 -0
  139. package/stat/assets/images/icon_playback-rate.svg +15 -0
  140. package/stat/assets/images/icon_return.png +0 -0
  141. package/stat/assets/images/icon_search_button.svg +8 -0
  142. package/stat/assets/images/icon_share.svg +9 -0
  143. package/stat/assets/images/icon_skip-ahead.svg +6 -0
  144. package/stat/assets/images/icon_skip-back.svg +13 -0
  145. package/stat/assets/images/icon_speaker.svg +18 -0
  146. package/stat/assets/images/icon_speaker_open.svg +10 -0
  147. package/stat/assets/images/icon_thumbnails.svg +12 -0
  148. package/stat/assets/images/icon_toc.svg +5 -0
  149. package/stat/assets/images/icon_two_pages.svg +9 -0
  150. package/stat/assets/images/icon_zoomer.png +0 -0
  151. package/stat/assets/images/loading.gif +0 -0
  152. package/stat/assets/images/logo_icon.png +0 -0
  153. package/stat/assets/images/marker_chap-off.png +0 -0
  154. package/stat/assets/images/marker_chap-off.svg +11 -0
  155. package/stat/assets/images/marker_chap-off_ia.png +0 -0
  156. package/stat/assets/images/marker_chap-on.png +0 -0
  157. package/stat/assets/images/marker_chap-on.svg +11 -0
  158. package/stat/assets/images/marker_srch-on.svg +11 -0
  159. package/stat/assets/images/marker_srchchap-off.png +0 -0
  160. package/stat/assets/images/marker_srchchap-on.png +0 -0
  161. package/stat/assets/images/nav_control-dn.png +0 -0
  162. package/stat/assets/images/nav_control-dn_ia.png +0 -0
  163. package/stat/assets/images/nav_control-up.png +0 -0
  164. package/stat/assets/images/nav_control-up_ia.png +0 -0
  165. package/stat/assets/images/nav_control.png +0 -0
  166. package/stat/assets/images/one_page_mode_icon.png +0 -0
  167. package/stat/assets/images/paper-badge.png +0 -0
  168. package/stat/assets/images/print_icon.png +0 -0
  169. package/stat/assets/images/progressbar.gif +0 -0
  170. package/stat/assets/images/right_edges.png +0 -0
  171. package/stat/assets/images/slider.png +0 -0
  172. package/stat/assets/images/slider_ia.png +0 -0
  173. package/stat/assets/images/thumbnail_mode_icon.png +0 -0
  174. package/stat/assets/images/transparent.png +0 -0
  175. package/stat/assets/images/two_page_mode_icon.png +0 -0
  176. package/stat/assets/images/zoom_in_icon.png +0 -0
  177. package/stat/assets/images/zoom_out_icon.png +0 -0
  178. package/stat/css/BookReader.scss +89 -0
  179. package/stat/css/_BRBookmarks.scss +29 -0
  180. package/stat/css/_BRComponent.scss +13 -0
  181. package/stat/css/_BRfloat.scss +197 -0
  182. package/stat/css/_BRicon.scss +48 -0
  183. package/stat/css/_BRmain.scss +251 -0
  184. package/stat/css/_BRnav.scss +359 -0
  185. package/stat/css/_BRpages.scss +139 -0
  186. package/stat/css/_BRsearch.scss +226 -0
  187. package/stat/css/_BRtoolbar.scss +84 -0
  188. package/stat/css/_BRvendor.scss +5 -0
  189. package/stat/css/_MobileNav.scss +194 -0
  190. package/stat/css/_TextSelection.scss +32 -0
  191. package/stat/css/_colorbox.scss +52 -0
  192. package/stat/css/_controls.scss +253 -0
  193. package/stat/css/_icons.scss +121 -0
  194. package/stat/jquery-wrapper.js +4 -0
  195. package/stat/plugins/plugin.archive_analytics.js +86 -0
  196. package/stat/plugins/plugin.autoplay.js +129 -0
  197. package/stat/plugins/plugin.chapters.js +248 -0
  198. package/stat/plugins/plugin.iframe.js +48 -0
  199. package/stat/plugins/plugin.mobile_nav.js +288 -0
  200. package/stat/plugins/plugin.resume.js +68 -0
  201. package/stat/plugins/plugin.text_selection.js +291 -0
  202. package/stat/plugins/plugin.url.js +198 -0
  203. package/stat/plugins/plugin.vendor-fullscreen.js +247 -0
  204. package/stat/plugins/search/plugin.search.js +439 -0
  205. package/stat/plugins/search/view.js +439 -0
  206. package/stat/plugins/tts/AbstractTTSEngine.js +249 -0
  207. package/stat/plugins/tts/FestivalTTSEngine.js +169 -0
  208. package/stat/plugins/tts/PageChunk.js +107 -0
  209. package/stat/plugins/tts/PageChunkIterator.js +163 -0
  210. package/stat/plugins/tts/WebTTSEngine.js +357 -0
  211. package/stat/plugins/tts/plugin.tts.js +357 -0
  212. package/stat/plugins/tts/tooltip_dict.js +15 -0
  213. package/stat/plugins/tts/utils.js +91 -0
  214. package/stat/util/browserSniffing.js +30 -0
  215. package/stat/util/debouncer.js +26 -0
  216. package/stat/util/docCookies.js +67 -0
  217. package/stat/util/strings.js +34 -0
  218. package/tests/e2e/viewmode.test.js +30 -30
  219. package/tests/jest/BookReader/BookReaderPublicFunctions.test.js +64 -52
  220. package/tests/jest/plugins/url/UrlPlugin.test.js +33 -10
  221. package/tests/karma/BookNavigator/book-navigator.test.js +413 -108
  222. package/tests/karma/BookNavigator/bookmarks/bookmark-button.test.js +44 -0
  223. package/tests/karma/BookNavigator/downloads/downloads-provider.test.js +6 -3
  224. package/tests/karma/BookNavigator/search/search-provider.test.js +106 -6
  225. package/tests/karma/BookNavigator/search/search-results.test.js +0 -2
  226. package/tests/karma/BookNavigator/sharing/sharing-provider.test.js +29 -20
  227. package/tests/karma/BookNavigator/volumes/volumes-provider.test.js +41 -17
  228. package/webpack.config.js +1 -1
  229. package/.nvmrc +0 -1
  230. package/BookReader/bookreader-component-bundle.js +0 -1436
  231. package/BookReader/bookreader-component-bundle.js.map +0 -1
  232. package/src/BookNavigator/assets/book-loader.js +0 -27
  233. package/src/ItemNavigator/ItemNavigator.js +0 -377
@@ -0,0 +1,518 @@
1
+ // @ts-check
2
+ import { DEFAULT_OPTIONS } from './options.js';
3
+ import { clamp } from './utils.js';
4
+ /** @typedef {import('./options.js').PageData} PageData */
5
+ /** @typedef {import('../BookReader.js').default} BookReader */
6
+
7
+ // URI to display when a page is not viewable.
8
+ // TODO Render configurable html for the user instead.
9
+ // FIXME Don't reference files on archive.org
10
+ const UNVIEWABLE_PAGE_URI = '/bookreader/static/preview-default.png';
11
+
12
+ /**
13
+ * Contains information about the Book/Document independent of the way it is
14
+ * being rendering. Nothing here should reference e.g. the mode, zoom, etc.
15
+ * It's just information about the book and its pages (usually as specified
16
+ * in the BookReader data option.)
17
+ */
18
+ export class BookModel {
19
+ /**
20
+ * @param {BookReader} br
21
+ */
22
+ constructor(br) {
23
+ this.br = br;
24
+ this.reduceSet = br.reduceSet;
25
+ this.ppi = br.options?.ppi ?? DEFAULT_OPTIONS.ppi;
26
+
27
+ /** @type {{width: number, height: number}} memoize storage */
28
+ this._medianPageSize = null;
29
+ /** @deprecated @type {{width: number, height: number}} memoize storage */
30
+ this._medianPageSizePixels = null;
31
+ /** @type {[PageData[], number]} */
32
+ this._getDataFlattenedCached = null;
33
+ }
34
+
35
+ /**
36
+ * @deprecated Use getMedianPageSizeInches
37
+ * Memoized
38
+ * @return {{width: number, height: number}}
39
+ */
40
+ getMedianPageSize() {
41
+ if (this._medianPageSizePixels) {
42
+ return this._medianPageSizePixels;
43
+ }
44
+
45
+ // A little expensive but we just do it once
46
+ const widths = [];
47
+ const heights = [];
48
+ for (let i = 0; i < this.getNumLeafs(); i++) {
49
+ widths.push(this.getPageWidth(i));
50
+ heights.push(this.getPageHeight(i));
51
+ }
52
+
53
+ widths.sort();
54
+ heights.sort();
55
+ this._medianPageSizePixels = {
56
+ width: widths[Math.floor(widths.length / 2)],
57
+ height: heights[Math.floor(heights.length / 2)]
58
+ };
59
+ return this._medianPageSizePixels;
60
+ }
61
+
62
+ /** Get median width/height of page in inches. Memoized for performance. */
63
+ getMedianPageSizeInches() {
64
+ if (this._medianPageSize) {
65
+ return this._medianPageSize;
66
+ }
67
+
68
+ const widths = [];
69
+ const heights = [];
70
+ for (const page of this.pagesIterator()) {
71
+ widths.push(page.widthInches);
72
+ heights.push(page.heightInches);
73
+ }
74
+
75
+ widths.sort();
76
+ heights.sort();
77
+
78
+ this._medianPageSize = {
79
+ width: widths[Math.floor(widths.length / 2)],
80
+ height: heights[Math.floor(heights.length / 2)]
81
+ };
82
+ return this._medianPageSize;
83
+ }
84
+
85
+ /**
86
+ * Returns the page width for the given index, or first or last page if out of range
87
+ * @deprecated see getPageWidth
88
+ * @param {PageIndex} index
89
+ */
90
+ _getPageWidth(index) {
91
+ // Synthesize a page width for pages not actually present in book.
92
+ // May or may not be the best approach.
93
+ // If index is out of range we return the width of first or last page
94
+ index = clamp(index, 0, this.getNumLeafs() - 1);
95
+ return this.getPageWidth(index);
96
+ }
97
+
98
+ /**
99
+ * Returns the page height for the given index, or first or last page if out of range
100
+ * @deprecated see getPageHeight
101
+ * @param {PageIndex} index
102
+ */
103
+ _getPageHeight(index) {
104
+ const clampedIndex = clamp(index, 0, this.getNumLeafs() - 1);
105
+ return this.getPageHeight(clampedIndex);
106
+ }
107
+
108
+ /**
109
+ * Returns the *highest* index the given page number, or undefined
110
+ * @param {PageNumString} pageNum
111
+ * @return {PageIndex|undefined}
112
+ */
113
+ getPageIndex(pageNum) {
114
+ const pageIndices = this.getPageIndices(pageNum);
115
+ return pageIndices.length ? pageIndices[pageIndices.length - 1] : undefined;
116
+ }
117
+
118
+ /**
119
+ * Returns an array (possibly empty) of the indices with the given page number
120
+ * @param {PageNumString} pageNum
121
+ * @return {PageIndex[]}
122
+ */
123
+ getPageIndices(pageNum) {
124
+ const indices = [];
125
+
126
+ // Check for special "nXX" page number
127
+ if (pageNum.slice(0,1) == 'n') {
128
+ try {
129
+ const pageIntStr = pageNum.slice(1, pageNum.length);
130
+ const pageIndex = parseInt(pageIntStr);
131
+ indices.push(pageIndex);
132
+ return indices;
133
+ } catch (err) {
134
+ // Do nothing... will run through page names and see if one matches
135
+ }
136
+ }
137
+
138
+ for (let i = 0; i < this.getNumLeafs(); i++) {
139
+ if (this.getPageNum(i) == pageNum) {
140
+ indices.push(i);
141
+ }
142
+ }
143
+
144
+ return indices;
145
+ }
146
+
147
+ /**
148
+ * Returns the name of the page as it should be displayed in the user interface
149
+ * @param {PageIndex} index
150
+ * @return {string}
151
+ */
152
+ getPageName(index) {
153
+ return 'Page ' + this.getPageNum(index);
154
+ }
155
+
156
+ /**
157
+ * @return {number} the total number of leafs (like an array length)
158
+ */
159
+ getNumLeafs() {
160
+ // For deprecated interface support, if numLeafs is set, use that.
161
+ if (this.br.numLeafs !== undefined)
162
+ return this.br.numLeafs;
163
+ return this._getDataFlattened().length;
164
+ }
165
+
166
+ /**
167
+ * @param {PageIndex} index
168
+ * @return {Number|undefined}
169
+ */
170
+ getPageWidth(index) {
171
+ return this.getPageProp(index, 'width');
172
+ }
173
+
174
+ /**
175
+ * @param {PageIndex} index
176
+ * @return {Number|undefined}
177
+ */
178
+ getPageHeight(index) {
179
+ return this.getPageProp(index, 'height');
180
+ }
181
+
182
+ /**
183
+ * @param {PageIndex} index
184
+ * @param {number} reduce - not used in default implementation
185
+ * @param {number} rotate - not used in default implementation
186
+ * @return {string|undefined}
187
+ */
188
+ // eslint-disable-next-line no-unused-vars
189
+ getPageURI(index, reduce, rotate) {
190
+ return !this.getPageProp(index, 'viewable', true) ? UNVIEWABLE_PAGE_URI : this.getPageProp(index, 'uri');
191
+ }
192
+
193
+ /**
194
+ * @param {PageIndex} index
195
+ * @return {'L' | 'R'}
196
+ */
197
+ getPageSide(index) {
198
+ return this.getPageProp(index, 'pageSide') || (index % 2 === 0 ? 'R' : 'L');
199
+ }
200
+
201
+ /**
202
+ * @param {PageIndex} index
203
+ * @return {PageNumString}
204
+ */
205
+ getPageNum(index) {
206
+ const pageNum = this.getPageProp(index, 'pageNum');
207
+ return pageNum === undefined ? `n${index}` : pageNum;
208
+ }
209
+
210
+ /**
211
+ * Generalized property accessor.
212
+ * @param {PageIndex} index
213
+ * @param {keyof PageData} propName
214
+ * @param {*} [fallbackValue] return if undefined
215
+ * @return {*|undefined}
216
+ */
217
+ getPageProp(index, propName, fallbackValue = undefined) {
218
+ return this._getDataProp(index, propName, fallbackValue);
219
+ }
220
+
221
+ /**
222
+ * This function returns the left and right indices for the user-visible
223
+ * spread that contains the given index.
224
+ * @note Can return indices out of range of what's in the book.
225
+ * @param {PageIndex} pindex
226
+ * @return {[PageIndex, PageIndex]} eg [0, 1]
227
+ */
228
+ getSpreadIndices(pindex) {
229
+ if (this.br.pageProgression == 'rl') {
230
+ return this.getPageSide(pindex) == 'R' ? [pindex + 1, pindex] : [pindex, pindex - 1];
231
+ } else {
232
+ return this.getPageSide(pindex) == 'L' ? [pindex, pindex + 1] : [pindex - 1, pindex];
233
+ }
234
+ }
235
+
236
+ /**
237
+ * Single images in the Internet Archive scandata.xml metadata are (somewhat incorrectly)
238
+ * given a "leaf" number. Some of these images from the scanning process should not
239
+ * be displayed in the BookReader (for example colour calibration cards). Since some
240
+ * of the scanned images will not be displayed in the BookReader (those marked with
241
+ * addToAccessFormats false in the scandata.xml) leaf numbers and BookReader page
242
+ * indexes are generally not the same. This function returns the BookReader page
243
+ * index given a scanned leaf number.
244
+ *
245
+ * This function is used, for example, to map between search results (that use the
246
+ * leaf numbers) and the displayed pages in the BookReader.
247
+ * @param {LeafNum} leafNum
248
+ * @return {PageIndex}
249
+ */
250
+ leafNumToIndex(leafNum) {
251
+ const index = this._getDataFlattened()
252
+ .findIndex(d => d.leafNum == leafNum);
253
+ // If no match is found, fall back to the leafNum provide (leafNum == index)
254
+ return index > -1 ? index : leafNum;
255
+ }
256
+
257
+ /**
258
+ * Parses the pageString format
259
+ * @param {PageString} pageString
260
+ * @return {PageIndex|undefined}
261
+ */
262
+ parsePageString(pageString) {
263
+ let pageIndex;
264
+ // Check for special "leaf"
265
+ const leafMatch = /^leaf(\d+)/.exec(pageString);
266
+ if (leafMatch) {
267
+ pageIndex = this.leafNumToIndex(parseInt(leafMatch[1], 10));
268
+ if (pageIndex === null) {
269
+ pageIndex = undefined; // to match return type of getPageIndex
270
+ }
271
+ } else {
272
+ pageIndex = this.getPageIndex(pageString);
273
+ }
274
+ return pageIndex;
275
+ }
276
+
277
+ /**
278
+ * @param {number} index use negatives to get page relative to end
279
+ * @param loop whether to loop (i.e. -1 == last page)
280
+ */
281
+ getPage(index, loop = true) {
282
+ const numLeafs = this.getNumLeafs();
283
+ if (!loop && (index < 0 || index >= numLeafs)) {
284
+ return undefined;
285
+ }
286
+ if (index < 0 && index >= -numLeafs) {
287
+ index += numLeafs;
288
+ }
289
+ index = index % numLeafs;
290
+ return new PageModel(this, index);
291
+ }
292
+
293
+ /**
294
+ * @param {object} [arg0]
295
+ * @param {number} [arg0.start] inclusive
296
+ * @param {number} [arg0.end] exclusive
297
+ * @param {boolean} [arg0.combineConsecutiveUnviewables] Yield only first unviewable
298
+ * of a chunk of unviewable pages instead of each page
299
+ */
300
+ * pagesIterator({ start = 0, end = Infinity, combineConsecutiveUnviewables = false } = {}) {
301
+ start = Math.max(0, start);
302
+ end = Math.min(end, this.getNumLeafs());
303
+
304
+ for (let i = start; i < end; i++) {
305
+ const page = this.getPage(i);
306
+ if (combineConsecutiveUnviewables && page.isConsecutiveUnviewable) continue;
307
+
308
+ yield page;
309
+ }
310
+ }
311
+
312
+ /**
313
+ * Flatten the nested structure (make 1d array), and also add pageSide prop
314
+ * @return {PageData[]}
315
+ */
316
+ _getDataFlattened() {
317
+ if (this._getDataFlattenedCached && this._getDataFlattenedCached[1] === this.br.data.length)
318
+ return this._getDataFlattenedCached[0];
319
+
320
+ let prevPageSide = null;
321
+ /** @type {number|null} */
322
+ let unviewablesChunkStart = null;
323
+ let index = 0;
324
+ // @ts-ignore TS doesn't know about flatMap for some reason
325
+ const flattened = this.br.data.flatMap(spread => {
326
+ return spread.map(page => {
327
+ if (!page.pageSide) {
328
+ if (prevPageSide === null) {
329
+ page.pageSide = spread.length === 2 ? 'L' : 'R';
330
+ } else {
331
+ page.pageSide = prevPageSide === 'L' ? 'R' : 'L';
332
+ }
333
+ }
334
+ prevPageSide = page.pageSide;
335
+
336
+ if (page.viewable === false) {
337
+ if (unviewablesChunkStart === null) {
338
+ page.unviewablesStart = unviewablesChunkStart = index;
339
+ } else {
340
+ page.unviewablesStart = unviewablesChunkStart;
341
+ }
342
+ } else {
343
+ unviewablesChunkStart = null;
344
+ }
345
+
346
+ index++;
347
+ return page;
348
+ });
349
+ });
350
+
351
+ // length is used as a cache breaker
352
+ this._getDataFlattenedCached = [flattened, this.br.data.length];
353
+ return flattened;
354
+ }
355
+
356
+ /**
357
+ * Helper. Return a prop for a given index. Returns `fallbackValue` if index is invalid or
358
+ * property not on page.
359
+ * @param {PageIndex} index
360
+ * @param {keyof PageData} prop
361
+ * @param {*} fallbackValue return if property not on the record
362
+ * @return {*}
363
+ */
364
+ _getDataProp(index, prop, fallbackValue = undefined) {
365
+ const dataf = this._getDataFlattened();
366
+ const invalidIndex = isNaN(index) || index < 0 || index >= dataf.length;
367
+ if (invalidIndex || 'undefined' == typeof(dataf[index][prop]))
368
+ return fallbackValue;
369
+ return dataf[index][prop];
370
+ }
371
+ }
372
+
373
+ /**
374
+ * A controlled schema for page data.
375
+ */
376
+ export class PageModel {
377
+ /**
378
+ * @param {BookModel} book
379
+ * @param {PageIndex} index
380
+ */
381
+ constructor(book, index) {
382
+ // TODO: Get default from config
383
+ this.ppi = book._getDataProp(index, 'ppi', book.ppi);
384
+ this.book = book;
385
+ this.index = index;
386
+ this.width = book.getPageWidth(index);
387
+ this.widthInches = this.width / this.ppi;
388
+ this.height = book.getPageHeight(index);
389
+ this.heightInches = this.height / this.ppi;
390
+ this.pageSide = book.getPageSide(index);
391
+ this.leafNum = book._getDataProp(index, 'leafNum', this.index);
392
+
393
+ /** @type {boolean} */
394
+ this.isViewable = book._getDataProp(index, 'viewable', true);
395
+ /** @type {PageIndex} The first in the series of unviewable pages this is in. */
396
+ this.unviewablesStart = book._getDataProp(index, 'unviewablesStart') || null;
397
+ /**
398
+ * Consecutive unviewable pages are pages in an unviewable "chunk" which are not the first
399
+ * of that chunk.
400
+ */
401
+ this.isConsecutiveUnviewable = !this.isViewable && this.unviewablesStart != this.index;
402
+
403
+ this._rawData = this.book._getDataFlattened()[this.index];
404
+ }
405
+
406
+ /**
407
+ * Updates the page to no longer be unviewable. Assumes the
408
+ * Page's URI is already set/correct.
409
+ */
410
+ makeViewable(newViewableState = true) {
411
+ if (this.isViewable == newViewableState) return;
412
+
413
+ if (newViewableState) {
414
+ this._rawData.viewable = true;
415
+ delete this._rawData.unviewablesStart;
416
+ // Update any subsequent page to now point to the right "start"
417
+ for (const page of this.book.pagesIterator({ start: this.index + 1 })) {
418
+ if (page.isViewable) break;
419
+ page._rawData.unviewablesStart = this.index + 1;
420
+ }
421
+ } else {
422
+ this._rawData.viewable = false;
423
+ this._rawData.unviewablesStart = (this.prev && !this.prev.isViewable) ? this.prev.unviewablesStart : this.index;
424
+ // Update any subsequent page to now point to the right "start"
425
+ for (const page of this.book.pagesIterator({ start: this.index + 1 })) {
426
+ if (!page.isViewable) break;
427
+ page._rawData.unviewablesStart = this._rawData.unviewablesStart;
428
+ }
429
+ }
430
+ }
431
+
432
+ get prev() {
433
+ return this.findPrev();
434
+ }
435
+
436
+ get next() {
437
+ return this.findNext();
438
+ }
439
+
440
+ /**
441
+ * @param {number} reduce
442
+ * @param {number} rotate
443
+ */
444
+ getURI(reduce, rotate) {
445
+ return this.book.getPageURI(this.index, reduce, rotate);
446
+ }
447
+
448
+ /**
449
+ * Returns the srcset with correct URIs or void string if out of range
450
+ * @param {number} reduce
451
+ * @param {number} [rotate]
452
+ */
453
+ getURISrcSet(reduce, rotate = 0) {
454
+ const { reduceSet } = this.book;
455
+ const initialReduce = reduceSet.floor(reduce);
456
+ // We don't need to repeat the initial reduce in the srcset
457
+ const topReduce = reduceSet.decr(initialReduce);
458
+ const reduces = [];
459
+ for (let r = topReduce; r >= 1; r = reduceSet.decr(r)) {
460
+ reduces.push(r);
461
+ }
462
+ return reduces
463
+ .map(r => `${this.getURI(r, rotate)} ${initialReduce / r}x`)
464
+ .join(', ');
465
+ }
466
+
467
+ /**
468
+ * @param {object} [arg0]
469
+ * @param {boolean} [arg0.combineConsecutiveUnviewables] Whether to only yield the first page
470
+ * of a series of unviewable pages instead of each page
471
+ * @return {PageModel|void}
472
+ */
473
+ findNext({ combineConsecutiveUnviewables = false } = {}) {
474
+ return this.book
475
+ .pagesIterator({ start: this.index + 1, combineConsecutiveUnviewables })
476
+ .next().value;
477
+ }
478
+
479
+ /**
480
+ * @param {object} [arg0]
481
+ * @param {boolean} [arg0.combineConsecutiveUnviewables] Whether to only yield the first page
482
+ * of a series of unviewable pages instead of each page
483
+ * @return {PageModel|void}
484
+ */
485
+ findPrev({ combineConsecutiveUnviewables = false } = {}) {
486
+ if (this.index == 0) return undefined;
487
+
488
+ if (combineConsecutiveUnviewables) {
489
+ if (this.isConsecutiveUnviewable) {
490
+ return this.book.getPage(this.unviewablesStart);
491
+ } else {
492
+ // Recursively goes backward through the book
493
+ // TODO make a reverse iterator to make it look identical to findNext
494
+ const prev = new PageModel(this.book, this.index - 1);
495
+ return prev.isViewable ? prev : prev.findPrev({ combineConsecutiveUnviewables });
496
+ }
497
+ } else {
498
+ return new PageModel(this.book, this.index - 1);
499
+ }
500
+ }
501
+ }
502
+
503
+ // There are a few main ways we can reference a specific page in a book:
504
+ /**
505
+ * @typedef {string} PageNumString
506
+ * Possible values: /^n?\d+$/. Example: 'n7', '18'
507
+ * Not necessarily unique
508
+ */
509
+ /**
510
+ * @typedef {number} LeafNum
511
+ * No clue if 0 or 1 indexed or consecutive; generally from IA book info.
512
+ */
513
+ /**
514
+ * @typedef {string} PageString
515
+ * Possible values: /^(leaf)?\d+$/ Example: 'leaf7', '18'
516
+ * If leaf-prefixed, then the number is a LeafNum. Otherwise it's a PageNumString
517
+ */
518
+ /** @typedef {number} PageIndex 0-based index of all the pages */
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Displays a console on the document for debugging devices where remote
3
+ * debugging is not feasible, and forwards all console.log's to be displayed
4
+ * on screen.
5
+ */
6
+ export class DebugConsole {
7
+ constructor() {
8
+ /** How many times we've seen the same line in a row */
9
+ this.currentRun = 0;
10
+ }
11
+
12
+ init() {
13
+ this.$log = $(`<div id="_debugLog" style="width: 100%; height: 300px; overflow: auto" />`);
14
+ $(document.body).prepend(this.$log);
15
+
16
+ this.$form = $(`
17
+ <form>
18
+ <input style="width:100%; font-family: monospace;" id="_debugLogInput">
19
+ </form>`);
20
+ this.$log.append(this.$form);
21
+
22
+ this.$form.on("submit", ev => {
23
+ ev.preventDefault();
24
+ const result = eval(this.$form.find('input').val());
25
+ this.logToScreen([result]);
26
+ });
27
+
28
+ const _realLog = console.log.bind(console);
29
+ console.log = (...args) => {
30
+ _realLog(...args);
31
+ this.logToScreen(args);
32
+ };
33
+
34
+ window.onerror = (...args) => this.logToScreen(args);
35
+ }
36
+
37
+ /**
38
+ * Log the provided array onto the on screen console
39
+ * @param {Array} args
40
+ */
41
+ logToScreen(args) {
42
+ const html = args.map(JSON.stringify).join(',');
43
+ const $lastEntry = this.$log.children('.log-entry:last-child');
44
+ if ($lastEntry.find('.entry-code').html() == html) {
45
+ $lastEntry.find('.count').text(`(${++this.currentRun})`);
46
+ } else {
47
+ this.currentRun = 1;
48
+ this.$log.append($(`
49
+ <div class="log-entry">
50
+ <code class="count"></code> <code class="entry-code">${html}</code>
51
+ </div>`));
52
+ }
53
+ }
54
+ }