@internetarchive/bookreader 5.0.0-26 → 5.0.0-29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (236) 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/BookReader/plugins/plugin.tts.js.map +1 -1
  11. package/BookReader/plugins/plugin.url.js +1 -1
  12. package/BookReader/plugins/plugin.url.js.map +1 -1
  13. package/BookReaderDemo/BookReaderDemo.css +14 -1
  14. package/BookReaderDemo/IADemoBr.js +104 -0
  15. package/BookReaderDemo/demo-internetarchive.html +65 -98
  16. package/CHANGELOG.md +10 -0
  17. package/package.json +9 -6
  18. package/src/BookNavigator/assets/ia-logo.js +17 -0
  19. package/src/BookNavigator/book-navigator.js +521 -0
  20. package/src/BookNavigator/bookmarks/bookmark-button.js +2 -1
  21. package/src/BookNavigator/bookmarks/bookmarks-provider.js +20 -8
  22. package/src/BookNavigator/bookmarks/ia-bookmarks.js +84 -51
  23. package/src/BookNavigator/downloads/downloads-provider.js +5 -9
  24. package/src/BookNavigator/downloads/downloads.js +1 -0
  25. package/src/BookNavigator/search/search-provider.js +15 -8
  26. package/src/BookNavigator/sharing.js +27 -0
  27. package/src/BookNavigator/visual-adjustments/visual-adjustments-provider.js +9 -8
  28. package/src/BookNavigator/volumes/volumes-provider.js +44 -13
  29. package/src/BookNavigator/volumes/volumes.js +14 -3
  30. package/src/BookReader/options.js +6 -0
  31. package/src/BookReader.js +20 -8
  32. package/src/BookReaderComponent/BookReaderComponent.js +53 -32
  33. package/src/css/_BRComponent.scss +1 -1
  34. package/src/plugins/search/plugin.search.js +10 -9
  35. package/src/plugins/tts/FestivalTTSEngine.js +1 -1
  36. package/src/plugins/url/UrlPlugin.js +184 -0
  37. package/src/plugins/url/plugin.url.js +220 -0
  38. package/{src → stat}/BookNavigator/BookModel.js +0 -0
  39. package/{src → stat}/BookNavigator/BookNavigator.js +109 -102
  40. package/stat/BookNavigator/assets/bookmark-colors.js +15 -0
  41. package/stat/BookNavigator/assets/button-base.js +61 -0
  42. package/stat/BookNavigator/assets/ia-logo.js +17 -0
  43. package/stat/BookNavigator/assets/icon_checkmark.js +6 -0
  44. package/stat/BookNavigator/assets/icon_close.js +3 -0
  45. package/stat/BookNavigator/assets/icon_sort_asc.js +5 -0
  46. package/stat/BookNavigator/assets/icon_sort_desc.js +5 -0
  47. package/stat/BookNavigator/assets/icon_sort_neutral.js +5 -0
  48. package/stat/BookNavigator/assets/icon_volumes.js +11 -0
  49. package/stat/BookNavigator/bookmarks/bookmark-button.js +64 -0
  50. package/stat/BookNavigator/bookmarks/bookmark-edit.js +215 -0
  51. package/stat/BookNavigator/bookmarks/bookmarks-list.js +285 -0
  52. package/stat/BookNavigator/bookmarks/bookmarks-loginCTA.js +28 -0
  53. package/stat/BookNavigator/bookmarks/bookmarks-provider.js +56 -0
  54. package/stat/BookNavigator/bookmarks/ia-bookmarks.js +523 -0
  55. package/{src → stat}/BookNavigator/br-fullscreen-mgr.js +1 -2
  56. package/stat/BookNavigator/delete-modal-actions.js +49 -0
  57. package/stat/BookNavigator/downloads/downloads-provider.js +72 -0
  58. package/stat/BookNavigator/downloads/downloads.js +139 -0
  59. package/stat/BookNavigator/provider-config.js +0 -0
  60. package/stat/BookNavigator/search/a-search-result.js +55 -0
  61. package/stat/BookNavigator/search/search-provider.js +180 -0
  62. package/stat/BookNavigator/search/search-results.js +360 -0
  63. package/{src/ItemNavigator/providers → stat/BookNavigator}/sharing.js +3 -5
  64. package/stat/BookNavigator/visual-adjustments/visual-adjustments-provider.js +94 -0
  65. package/stat/BookNavigator/visual-adjustments/visual-adjustments.js +280 -0
  66. package/stat/BookNavigator/volumes/volumes-provider.js +83 -0
  67. package/stat/BookNavigator/volumes/volumes.js +178 -0
  68. package/stat/BookReader/BookModel.js +518 -0
  69. package/stat/BookReader/DebugConsole.js +54 -0
  70. package/stat/BookReader/DragScrollable.js +233 -0
  71. package/stat/BookReader/ImageCache.js +116 -0
  72. package/stat/BookReader/Mode1Up.js +102 -0
  73. package/stat/BookReader/Mode1UpLit.js +434 -0
  74. package/stat/BookReader/Mode2Up.js +1372 -0
  75. package/stat/BookReader/ModeSmoothZoom.js +177 -0
  76. package/stat/BookReader/ModeThumb.js +344 -0
  77. package/stat/BookReader/Navbar/Navbar.js +310 -0
  78. package/stat/BookReader/PageContainer.js +120 -0
  79. package/stat/BookReader/ReduceSet.js +26 -0
  80. package/stat/BookReader/Toolbar/Toolbar.js +384 -0
  81. package/stat/BookReader/events.js +20 -0
  82. package/stat/BookReader/options.js +324 -0
  83. package/stat/BookReader/utils/HTMLDimensionsCacher.js +44 -0
  84. package/stat/BookReader/utils/classes.js +36 -0
  85. package/stat/BookReader/utils.js +240 -0
  86. package/stat/BookReader.js +2550 -0
  87. package/stat/BookReaderComponent/BookReaderComponent.js +117 -0
  88. package/stat/assets/icons/1up.svg +12 -0
  89. package/stat/assets/icons/2up.svg +15 -0
  90. package/stat/assets/icons/advance.svg +26 -0
  91. package/stat/assets/icons/chevron-right.svg +1 -0
  92. package/stat/assets/icons/close-circle-dark.svg +1 -0
  93. package/stat/assets/icons/close-circle.svg +1 -0
  94. package/stat/assets/icons/fullscreen.svg +17 -0
  95. package/stat/assets/icons/fullscreen_exit.svg +17 -0
  96. package/stat/assets/icons/hamburger.svg +15 -0
  97. package/stat/assets/icons/left-arrow.svg +12 -0
  98. package/stat/assets/icons/magnify-minus.svg +16 -0
  99. package/stat/assets/icons/magnify-plus.svg +17 -0
  100. package/stat/assets/icons/magnify.svg +15 -0
  101. package/stat/assets/icons/pause.svg +23 -0
  102. package/stat/assets/icons/play.svg +22 -0
  103. package/stat/assets/icons/playback-speed.svg +34 -0
  104. package/stat/assets/icons/read-aloud.svg +22 -0
  105. package/stat/assets/icons/review.svg +22 -0
  106. package/stat/assets/icons/thumbnails.svg +17 -0
  107. package/stat/assets/icons/voice.svg +1 -0
  108. package/stat/assets/icons/volume-full.svg +22 -0
  109. package/stat/assets/images/BRicons.png +0 -0
  110. package/stat/assets/images/BRicons.svg +94 -0
  111. package/stat/assets/images/BRicons_ia.png +0 -0
  112. package/stat/assets/images/back_pages.png +0 -0
  113. package/stat/assets/images/book_bottom_icon.png +0 -0
  114. package/stat/assets/images/book_down_icon.png +0 -0
  115. package/stat/assets/images/book_left_icon.png +0 -0
  116. package/stat/assets/images/book_leftmost_icon.png +0 -0
  117. package/stat/assets/images/book_right_icon.png +0 -0
  118. package/stat/assets/images/book_rightmost_icon.png +0 -0
  119. package/stat/assets/images/book_top_icon.png +0 -0
  120. package/stat/assets/images/book_up_icon.png +0 -0
  121. package/stat/assets/images/books_graphic.svg +177 -0
  122. package/stat/assets/images/booksplit.png +0 -0
  123. package/stat/assets/images/control_pause_icon.png +0 -0
  124. package/stat/assets/images/control_play_icon.png +0 -0
  125. package/stat/assets/images/embed_icon.png +0 -0
  126. package/stat/assets/images/icon-home-ia.png +0 -0
  127. package/stat/assets/images/icon_OL-logo-xs.png +0 -0
  128. package/stat/assets/images/icon_alert-xs.png +0 -0
  129. package/stat/assets/images/icon_book.svg +12 -0
  130. package/stat/assets/images/icon_bookmark.svg +12 -0
  131. package/stat/assets/images/icon_close-pop.png +0 -0
  132. package/stat/assets/images/icon_download.png +0 -0
  133. package/stat/assets/images/icon_gear.svg +14 -0
  134. package/stat/assets/images/icon_hamburger.svg +20 -0
  135. package/stat/assets/images/icon_home.png +0 -0
  136. package/stat/assets/images/icon_home.svg +21 -0
  137. package/stat/assets/images/icon_home_ia.png +0 -0
  138. package/stat/assets/images/icon_indicator.png +0 -0
  139. package/stat/assets/images/icon_info.svg +11 -0
  140. package/stat/assets/images/icon_one_page.svg +8 -0
  141. package/stat/assets/images/icon_pause.svg +1 -0
  142. package/stat/assets/images/icon_play.svg +1 -0
  143. package/stat/assets/images/icon_playback-rate.svg +15 -0
  144. package/stat/assets/images/icon_return.png +0 -0
  145. package/stat/assets/images/icon_search_button.svg +8 -0
  146. package/stat/assets/images/icon_share.svg +9 -0
  147. package/stat/assets/images/icon_skip-ahead.svg +6 -0
  148. package/stat/assets/images/icon_skip-back.svg +13 -0
  149. package/stat/assets/images/icon_speaker.svg +18 -0
  150. package/stat/assets/images/icon_speaker_open.svg +10 -0
  151. package/stat/assets/images/icon_thumbnails.svg +12 -0
  152. package/stat/assets/images/icon_toc.svg +5 -0
  153. package/stat/assets/images/icon_two_pages.svg +9 -0
  154. package/stat/assets/images/icon_zoomer.png +0 -0
  155. package/stat/assets/images/loading.gif +0 -0
  156. package/stat/assets/images/logo_icon.png +0 -0
  157. package/stat/assets/images/marker_chap-off.png +0 -0
  158. package/stat/assets/images/marker_chap-off.svg +11 -0
  159. package/stat/assets/images/marker_chap-off_ia.png +0 -0
  160. package/stat/assets/images/marker_chap-on.png +0 -0
  161. package/stat/assets/images/marker_chap-on.svg +11 -0
  162. package/stat/assets/images/marker_srch-on.svg +11 -0
  163. package/stat/assets/images/marker_srchchap-off.png +0 -0
  164. package/stat/assets/images/marker_srchchap-on.png +0 -0
  165. package/stat/assets/images/nav_control-dn.png +0 -0
  166. package/stat/assets/images/nav_control-dn_ia.png +0 -0
  167. package/stat/assets/images/nav_control-up.png +0 -0
  168. package/stat/assets/images/nav_control-up_ia.png +0 -0
  169. package/stat/assets/images/nav_control.png +0 -0
  170. package/stat/assets/images/one_page_mode_icon.png +0 -0
  171. package/stat/assets/images/paper-badge.png +0 -0
  172. package/stat/assets/images/print_icon.png +0 -0
  173. package/stat/assets/images/progressbar.gif +0 -0
  174. package/stat/assets/images/right_edges.png +0 -0
  175. package/stat/assets/images/slider.png +0 -0
  176. package/stat/assets/images/slider_ia.png +0 -0
  177. package/stat/assets/images/thumbnail_mode_icon.png +0 -0
  178. package/stat/assets/images/transparent.png +0 -0
  179. package/stat/assets/images/two_page_mode_icon.png +0 -0
  180. package/stat/assets/images/zoom_in_icon.png +0 -0
  181. package/stat/assets/images/zoom_out_icon.png +0 -0
  182. package/stat/css/BookReader.scss +89 -0
  183. package/stat/css/_BRBookmarks.scss +29 -0
  184. package/stat/css/_BRComponent.scss +13 -0
  185. package/stat/css/_BRfloat.scss +197 -0
  186. package/stat/css/_BRicon.scss +48 -0
  187. package/stat/css/_BRmain.scss +251 -0
  188. package/stat/css/_BRnav.scss +359 -0
  189. package/stat/css/_BRpages.scss +139 -0
  190. package/stat/css/_BRsearch.scss +226 -0
  191. package/stat/css/_BRtoolbar.scss +84 -0
  192. package/stat/css/_BRvendor.scss +5 -0
  193. package/stat/css/_MobileNav.scss +194 -0
  194. package/stat/css/_TextSelection.scss +32 -0
  195. package/stat/css/_colorbox.scss +52 -0
  196. package/stat/css/_controls.scss +253 -0
  197. package/stat/css/_icons.scss +121 -0
  198. package/stat/jquery-wrapper.js +4 -0
  199. package/stat/plugins/plugin.archive_analytics.js +86 -0
  200. package/stat/plugins/plugin.autoplay.js +129 -0
  201. package/stat/plugins/plugin.chapters.js +248 -0
  202. package/stat/plugins/plugin.iframe.js +48 -0
  203. package/stat/plugins/plugin.mobile_nav.js +288 -0
  204. package/stat/plugins/plugin.resume.js +68 -0
  205. package/stat/plugins/plugin.text_selection.js +291 -0
  206. package/{src → stat}/plugins/plugin.url.js +0 -0
  207. package/stat/plugins/plugin.vendor-fullscreen.js +247 -0
  208. package/stat/plugins/search/plugin.search.js +439 -0
  209. package/stat/plugins/search/view.js +439 -0
  210. package/stat/plugins/tts/AbstractTTSEngine.js +249 -0
  211. package/stat/plugins/tts/FestivalTTSEngine.js +169 -0
  212. package/stat/plugins/tts/PageChunk.js +107 -0
  213. package/stat/plugins/tts/PageChunkIterator.js +163 -0
  214. package/stat/plugins/tts/WebTTSEngine.js +357 -0
  215. package/stat/plugins/tts/plugin.tts.js +357 -0
  216. package/stat/plugins/tts/tooltip_dict.js +15 -0
  217. package/stat/plugins/tts/utils.js +91 -0
  218. package/stat/util/browserSniffing.js +30 -0
  219. package/stat/util/debouncer.js +26 -0
  220. package/stat/util/docCookies.js +67 -0
  221. package/stat/util/strings.js +34 -0
  222. package/tests/e2e/viewmode.test.js +30 -30
  223. package/tests/jest/BookReader/BookReaderPublicFunctions.test.js +64 -52
  224. package/tests/jest/BookReader.test.js +1 -1
  225. package/tests/jest/plugins/url/UrlPlugin.test.js +175 -0
  226. package/tests/jest/plugins/{plugin.url.test.js → url/plugin.url.test.js} +3 -2
  227. package/tests/karma/BookNavigator/book-navigator.test.js +413 -108
  228. package/tests/karma/BookNavigator/bookmarks/bookmark-button.test.js +44 -0
  229. package/tests/karma/BookNavigator/downloads/downloads-provider.test.js +6 -3
  230. package/tests/karma/BookNavigator/search/search-provider.test.js +106 -6
  231. package/tests/karma/BookNavigator/search/search-results.test.js +0 -2
  232. package/tests/karma/BookNavigator/sharing/sharing-provider.test.js +29 -20
  233. package/tests/karma/BookNavigator/volumes/volumes-provider.test.js +46 -22
  234. package/webpack.config.js +1 -1
  235. package/src/BookNavigator/assets/book-loader.js +0 -27
  236. package/src/ItemNavigator/ItemNavigator.js +0 -377
@@ -0,0 +1,521 @@
1
+ // eslint-disable-next-line no-unused-vars
2
+ import { SharedResizeObserver } from '@internetarchive/shared-resize-observer';
3
+ // eslint-disable-next-line no-unused-vars
4
+ import { ModalManager } from '@internetarchive/modal-manager';
5
+ import { css, html, LitElement } from 'lit-element';
6
+ import SearchProvider from './search/search-provider.js';
7
+ import DownloadProvider from './downloads/downloads-provider.js';
8
+ import VisualAdjustmentProvider from './visual-adjustments/visual-adjustments-provider.js';
9
+ import BookmarksProvider from './bookmarks/bookmarks-provider.js';
10
+ import SharingProvider from './sharing.js';
11
+ import VolumesProvider from './volumes/volumes-provider.js';
12
+ import iaLogo from './assets/ia-logo.js';
13
+
14
+ const events = {
15
+ menuUpdated: 'menuUpdated',
16
+ updateSideMenu: 'updateSideMenu',
17
+ PostInit: 'PostInit',
18
+ ViewportInFullScreen: 'ViewportInFullScreen',
19
+ };
20
+ export class BookNavigator extends LitElement {
21
+ static get properties() {
22
+ return {
23
+ itemMD: { type: Object },
24
+ bookReaderLoaded: { type: Boolean },
25
+ bookreader: { type: Object },
26
+ bookIsRestricted: { type: Boolean },
27
+ downloadableTypes: { type: Array },
28
+ isAdmin: { type: Boolean },
29
+ lendingInitialized: { type: Boolean },
30
+ lendingStatus: { type: Object },
31
+ menuProviders: { type: Object },
32
+ menuShortcuts: { type: Array },
33
+ signedIn: { type: Boolean },
34
+ loaded: { type: Boolean },
35
+ sharedObserver: { type: Object, attribute: false },
36
+ modal: { type: Object, attribute: false },
37
+ fullscreenBranding: { type: Object },
38
+ };
39
+ }
40
+
41
+ constructor() {
42
+ super();
43
+ this.itemMD = undefined;
44
+ this.loaded = false;
45
+ this.bookReaderCannotLoad = false;
46
+ this.bookReaderLoaded = false;
47
+ this.bookreader = null;
48
+ this.bookIsRestricted = false;
49
+ this.downloadableTypes = [];
50
+ this.isAdmin = false;
51
+ this.lendingInitialized = false;
52
+ this.lendingStatus = {};
53
+ this.menuProviders = {};
54
+ this.menuShortcuts = [];
55
+ this.signedIn = false;
56
+ /** @type {ModalManager} */
57
+ this.modal = undefined;
58
+ /** @type {SharedResizeObserver} */
59
+ this.sharedObserver = undefined;
60
+ this.fullscreenBranding = iaLogo;
61
+ // Untracked properties
62
+ this.sharedObserverHandler = undefined;
63
+ this.brWidth = 0;
64
+ this.brHeight = 0;
65
+ this.shortcutOrder = [
66
+ /**
67
+ * sets exit FS button (`this.fullscreenBranding1)
68
+ * when `br.options.enableFSLogoShortcut`
69
+ */
70
+ 'fullscreen',
71
+ 'volumes',
72
+ 'search',
73
+ 'bookmarks'
74
+ ];
75
+ }
76
+
77
+ disconnectedCallback() {
78
+ this.sharedObserver.removeObserver({
79
+ target: this.mainBRContainer,
80
+ handler: this.sharedObserverHandler
81
+ });
82
+ }
83
+
84
+ firstUpdated() {
85
+ this.bindEventListeners();
86
+ this.emitPostInit();
87
+ this.loaded = true;
88
+ }
89
+
90
+ updated(changed) {
91
+ if (!this.bookreader || !this.itemMD || !this.bookReaderLoaded) {
92
+ return;
93
+ }
94
+
95
+ const reload = changed.has('loaded') && this.loaded;
96
+ if (reload
97
+ || changed.has('itemMD')
98
+ || changed.has('bookreader')
99
+ || changed.has('signedIn')
100
+ || changed.has('isAdmin')
101
+ || changed.has('modal')) {
102
+ this.initializeBookSubmenus();
103
+ }
104
+
105
+ if (changed.has('sharedObserver') && this.bookreader) {
106
+ this.loadSharedObserver();
107
+ }
108
+ }
109
+
110
+ /**
111
+ * Global event emitter for when Book Navigator loads
112
+ */
113
+ emitPostInit() {
114
+ // emit global event when book nav has loaded with current bookreader selector
115
+ this.dispatchEvent(new CustomEvent(`BrBookNav:${events.PostInit}`, {
116
+ detail: { brSelector: this.bookreader?.el },
117
+ bubbles: true,
118
+ composed: true,
119
+ }));
120
+ }
121
+
122
+ /**
123
+ * @typedef {{
124
+ * baseHost: string,
125
+ * modal: ModalManager,
126
+ * sharedObserver: SharedResizeObserver,
127
+ * bookreader: BookReader,
128
+ * item: Item,
129
+ * signedIn: boolean,
130
+ * isAdmin: boolean,
131
+ * onProviderChange: (BookReader, object) => void,
132
+ * }} baseProviderConfig
133
+ *
134
+ * @return {baseProviderConfig}
135
+ */
136
+ get baseProviderConfig() {
137
+ return {
138
+ baseHost: this.baseHost,
139
+ modal: this.modal,
140
+ sharedObserver: this.sharedObserver,
141
+ bookreader: this.bookreader,
142
+ item: this.itemMD,
143
+ signedIn: this.signedIn,
144
+ isAdmin: this.isAdmin,
145
+ onProviderChange: () => {}
146
+ };
147
+ }
148
+
149
+ /**
150
+ * Instantiates books submenus & their update callbacks
151
+ *
152
+ * NOTE: we are doing our best to scope bookreader's instance.
153
+ * If your submenu provider uses a bookreader instance to read, manually
154
+ * manipulate BookReader, please update the navigator's instance of it
155
+ * to keep it in sync.
156
+ */
157
+ initializeBookSubmenus() {
158
+ const providers = {
159
+ downloads: new DownloadProvider(this.baseProviderConfig),
160
+ share: new SharingProvider(this.baseProviderConfig),
161
+ visualAdjustments: new VisualAdjustmentProvider({
162
+ ...this.baseProviderConfig,
163
+ /** Update menu contents */
164
+ onProviderChange: () => {
165
+ this.updateMenuContents();
166
+ },
167
+ }),
168
+ };
169
+
170
+ if (this.bookreader.options.enableSearch) {
171
+ providers.search = new SearchProvider({
172
+ ...this.baseProviderConfig,
173
+ /**
174
+ * Search specific menu updates
175
+ * @param {BookReader} brInstance
176
+ * @param {{ searchCanceled: boolean }} searchUpdates
177
+ */
178
+ onProviderChange: (brInstance = null, searchUpdates = {}) => {
179
+ if (brInstance) {
180
+ /* refresh br instance reference */
181
+ this.bookreader = brInstance;
182
+ }
183
+ const wideEnoughToOpenMenu = this.brWidth >= 640;
184
+ if (wideEnoughToOpenMenu && !searchUpdates?.searchCanceled) {
185
+ /* open side search menu */
186
+ setTimeout(() => {
187
+ this.updateSideMenu('search', 'open');
188
+ }, 0);
189
+ }
190
+ this.updateMenuContents();
191
+ },
192
+ });
193
+ }
194
+
195
+ if (this.bookreader.options.enableBookmarks) {
196
+ providers.bookmarks = new BookmarksProvider({
197
+ ...this.baseProviderConfig,
198
+ onProviderChange: (bookmarks) => {
199
+ const method = Object.keys(bookmarks).length ? 'add' : 'remove';
200
+ this[`${method}MenuShortcut`]('bookmarks');
201
+ this.updateMenuContents();
202
+ }
203
+ });
204
+ }
205
+
206
+ // add shortcut for volumes if multipleBooksList exists
207
+ if (this.bookreader.options.enableMultipleBooks) {
208
+ providers.volumes = new VolumesProvider({
209
+ ...this.baseProviderConfig,
210
+ onProviderChange: (brInstance = null, volumesUpdates = {}) => {
211
+ if (brInstance) {
212
+ /* refresh br instance reference */
213
+ this.bookreader = brInstance;
214
+ }
215
+ this.updateMenuContents();
216
+ this.updateSideMenu('volumes', 'open');
217
+ }
218
+ });
219
+ }
220
+
221
+ this.menuProviders = providers;
222
+ this.addMenuShortcut('search');
223
+ this.addMenuShortcut('volumes');
224
+ this.updateMenuContents();
225
+ }
226
+
227
+ /** gets element that houses the bookreader in light dom */
228
+ get mainBRContainer() {
229
+ return document.querySelector(this.bookreader?.el);
230
+ }
231
+
232
+ /** Fullscreen Shortcut */
233
+ addFullscreenShortcut() {
234
+ const closeFS = {
235
+ icon: this.fullscreenShortcut,
236
+ id: 'fullscreen',
237
+ };
238
+ this.menuShortcuts.push(closeFS);
239
+ this.sortMenuShortcuts();
240
+ this.emitMenuShortcutsUpdated();
241
+ }
242
+
243
+ deleteFullscreenShortcut() {
244
+ const updatedShortcuts = this.menuShortcuts.filter(({ id }) => {
245
+ return id !== 'fullscreen';
246
+ });
247
+ this.menuShortcuts = updatedShortcuts;
248
+ this.sortMenuShortcuts();
249
+ this.emitMenuShortcutsUpdated();
250
+ }
251
+
252
+ closeFullscreen() {
253
+ this.bookreader.exitFullScreen();
254
+ }
255
+
256
+ get fullscreenShortcut() {
257
+ return html`
258
+ <button
259
+ @click=${() => this.closeFullscreen()}
260
+ title="Exit fullscreen view"
261
+ >${this.fullscreenBranding}</button>
262
+ `;
263
+ }
264
+ /** End Fullscreen Shortcut */
265
+
266
+ /**
267
+ * Open side menu
268
+ * @param {string} menuId
269
+ * @param {('open'|'close'|'toggle')} action
270
+ */
271
+ updateSideMenu(menuId = '', action = 'open') {
272
+ if (!menuId) {
273
+ return;
274
+ }
275
+ const event = new CustomEvent(
276
+ events.updateSideMenu, {
277
+ detail: { menuId, action },
278
+ },
279
+ );
280
+ this.dispatchEvent(event);
281
+ }
282
+
283
+ /**
284
+ * Sets order of menu and emits custom event when done
285
+ */
286
+ updateMenuContents() {
287
+ const {
288
+ search, downloads, visualAdjustments, share, bookmarks, volumes
289
+ } = this.menuProviders;
290
+ const availableMenus = [volumes, search, bookmarks, visualAdjustments, share].filter((menu) => !!menu);
291
+
292
+ if (this.shouldShowDownloadsMenu()) {
293
+ downloads?.update(this.downloadableTypes);
294
+ availableMenus.splice(1, 0, downloads);
295
+ }
296
+
297
+ const event = new CustomEvent(
298
+ events.menuUpdated, {
299
+ detail: availableMenus,
300
+ },
301
+ );
302
+ this.dispatchEvent(event);
303
+ }
304
+
305
+ /**
306
+ * Confirms if we should show the downloads menu
307
+ * @returns {bool}
308
+ */
309
+ shouldShowDownloadsMenu() {
310
+ if (this.bookIsRestricted === false) { return true; }
311
+ if (this.isAdmin) { return true; }
312
+ const { user_loan_record = {} } = this.lendingStatus;
313
+ const hasNoLoanRecord = Array.isArray(user_loan_record); /* (bc PHP assoc. arrays) */
314
+
315
+ if (hasNoLoanRecord) { return false; }
316
+
317
+ const hasValidLoan = user_loan_record.type && (user_loan_record.type !== 'SESSION_LOAN');
318
+ return hasValidLoan;
319
+ }
320
+
321
+ /**
322
+ * Adds a provider object to the menuShortcuts array property if it isn't
323
+ * already added. menuShortcuts are then sorted by shortcutOrder and
324
+ * a menuShortcutsUpdated event is emitted.
325
+ *
326
+ * @param {string} menuId - a string matching the id property of a provider
327
+ */
328
+ addMenuShortcut(menuId) {
329
+ if (this.menuShortcuts.find((m) => m.id === menuId)) {
330
+ // menu is already there
331
+ return;
332
+ }
333
+
334
+ if (!this.menuProviders[menuId]) {
335
+ // no provider for this menu
336
+ return;
337
+ }
338
+
339
+ this.menuShortcuts.push(this.menuProviders[menuId]);
340
+
341
+ this.sortMenuShortcuts();
342
+ this.emitMenuShortcutsUpdated();
343
+ }
344
+
345
+ /**
346
+ * Removes a provider object from the menuShortcuts array and emits a
347
+ * menuShortcutsUpdated event.
348
+ *
349
+ * @param {string} menuId - a string matching the id property of a provider
350
+ */
351
+ removeMenuShortcut(menuId) {
352
+ this.menuShortcuts = this.menuShortcuts.filter((m) => m.id !== menuId);
353
+ this.emitMenuShortcutsUpdated();
354
+ }
355
+
356
+ /**
357
+ * Sorts the menuShortcuts property by comparing each provider's id to
358
+ * the id in each iteration over the shortcutOrder array.
359
+ */
360
+ sortMenuShortcuts() {
361
+ this.menuShortcuts = this.shortcutOrder.reduce((shortcuts, id) => {
362
+ const menu = this.menuShortcuts.find((m) => m.id === id);
363
+ if (menu) { shortcuts.push(menu); }
364
+ return shortcuts;
365
+ }, []);
366
+ }
367
+
368
+ emitMenuShortcutsUpdated() {
369
+ const event = new CustomEvent('menuShortcutsUpdated', {
370
+ detail: this.menuShortcuts,
371
+ });
372
+ this.dispatchEvent(event);
373
+ }
374
+
375
+ emitLoadingStatusUpdate(loaded) {
376
+ const event = new CustomEvent('loadingStateUpdated', {
377
+ detail: { loaded },
378
+ });
379
+ this.dispatchEvent(event);
380
+ }
381
+
382
+ /**
383
+ * Core bookreader event handler registry
384
+ *
385
+ * NOTE: we are trying to keep bookreader's instance in scope
386
+ * Please update Book Navigator's instance reference of it to keep it current
387
+ */
388
+ bindEventListeners() {
389
+ window.addEventListener('BookReader:PostInit', (e) => {
390
+ this.bookreader = e.detail.props;
391
+ this.bookReaderLoaded = true;
392
+ this.bookReaderCannotLoad = false;
393
+ this.emitLoadingStatusUpdate(true);
394
+ this.loadSharedObserver();
395
+ setTimeout(() => {
396
+ this.bookreader.resize();
397
+ }, 0);
398
+ });
399
+ window.addEventListener('BookReader:fullscreenToggled', (event) => {
400
+ const { detail: { props: brInstance = null } } = event;
401
+ if (brInstance) {
402
+ this.bookreader = brInstance;
403
+ }
404
+ this.manageFullScreenBehavior();
405
+ }, { passive: true });
406
+ window.addEventListener('BookReader:ToggleSearchMenu', (event) => {
407
+ this.dispatchEvent(new CustomEvent(events.updateSideMenu, {
408
+ detail: { menuId: 'search', action: 'toggle' },
409
+ }));
410
+ });
411
+ window.addEventListener('LendingFlow:PostInit', ({ detail }) => {
412
+ const {
413
+ downloadTypesAvailable, lendingStatus, isAdmin, previewType,
414
+ } = detail;
415
+ this.lendingInitialized = true;
416
+ this.downloadableTypes = downloadTypesAvailable;
417
+ this.lendingStatus = lendingStatus;
418
+ this.isAdmin = isAdmin;
419
+ this.bookReaderCannotLoad = previewType === 'singlePagePreview';
420
+ });
421
+ window.addEventListener('BRJSIA:PostInit', ({ detail }) => {
422
+ const { isRestricted, downloadURLs } = detail;
423
+ this.bookReaderLoaded = true;
424
+ this.downloadableTypes = downloadURLs;
425
+ this.bookIsRestricted = isRestricted;
426
+ });
427
+ }
428
+
429
+ loadSharedObserver() {
430
+ this.sharedObserverHandler = { handleResize: this.handleResize.bind(this) };
431
+ this.sharedObserver?.addObserver({
432
+ target: this.mainBRContainer,
433
+ handler: this.sharedObserverHandler
434
+ });
435
+ }
436
+
437
+ /**
438
+ * Uses resize observer to fire BookReader's `resize` functionality
439
+ * We do not want to trigger resize IF:
440
+ * - book animation is happening
441
+ * - book is in fullscreen (fullscreen is handled separately)
442
+ *
443
+ * @param { target: HTMLElement, contentRect: DOMRectReadOnly } entry
444
+ */
445
+ handleResize({ contentRect, target }) {
446
+ const startBrWidth = this.brWidth;
447
+ const startBrHeight = this.brHeight;
448
+ const { animating } = this.bookreader;
449
+
450
+ if (target === this.mainBRContainer) {
451
+ this.brWidth = contentRect.width;
452
+ this.brHeight = contentRect.height;
453
+ }
454
+
455
+ const widthChange = startBrWidth !== this.brWidth;
456
+ const heightChange = startBrHeight !== this.brHeight;
457
+
458
+ if (!animating && (widthChange || heightChange)) {
459
+ this.bookreader.resize();
460
+ }
461
+ }
462
+
463
+ /**
464
+ * Manages Fullscreen behavior
465
+ * This makes sure that controls are _always_ in view
466
+ * We need this to accommodate LOAN BAR during fullscreen
467
+ */
468
+ manageFullScreenBehavior() {
469
+ this.emitFullScreenState();
470
+
471
+ if (!this.bookreader.options.enableFSLogoShortcut) {
472
+ return;
473
+ }
474
+
475
+ const isFullScreen = this.bookreader.isFullscreen();
476
+ if (isFullScreen) {
477
+ this.addFullscreenShortcut();
478
+ } else {
479
+ this.deleteFullscreenShortcut();
480
+ }
481
+ }
482
+
483
+ /**
484
+ * Relays fullscreen toggle events
485
+ */
486
+ emitFullScreenState() {
487
+ const isFullScreen = this.bookreader.isFullscreen();
488
+ const event = new CustomEvent('ViewportInFullScreen', {
489
+ detail: { isFullScreen },
490
+ });
491
+ this.dispatchEvent(event);
492
+ }
493
+
494
+ get loadingClass() {
495
+ return !this.bookReaderLoaded ? 'loading' : '';
496
+ }
497
+
498
+ get itemImage() {
499
+ const url = `https://${this.baseHost}/services/img/${this.item.metadata.identifier}`;
500
+ return html`<img class="cover-img" src=${url} alt="cover image for ${this.item.metadata.identifier}">`;
501
+ }
502
+
503
+ render() {
504
+ const placeholder = this.bookReaderCannotLoad ? this.itemImage : this.loader;
505
+ return html`<div id="book-navigator" class="${this.loadingClass}">
506
+ ${placeholder}
507
+ <slot name="theater-main"></slot>
508
+ </div>
509
+ `;
510
+ }
511
+
512
+ static get styles() {
513
+ return css`
514
+ .cover-img {
515
+ max-height: 300px;
516
+ }
517
+ `;
518
+ }
519
+ }
520
+
521
+ customElements.define('book-navigator', BookNavigator);
@@ -12,7 +12,7 @@ export default class BookmarkButton extends LitElement {
12
12
  height: 4rem;
13
13
  width: 4rem;
14
14
  background: transparent;
15
- cursor: url('/images/bookreader/bookmark-add.png'), pointer;
15
+ cursor: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' height='24' viewBox='0 0 16 24' width='16'%3E%3Cg fill='%23333' fill-rule='evenodd'%3E%3Cpath d='m15 0c.5522847 0 1 .44771525 1 1v23l-8-5.4545455-8 5.4545455v-23c0-.55228475.44771525-1 1-1zm-2 2h-10c-.51283584 0-.93550716.38604019-.99327227.88337887l-.00672773.11662113v18l6-4.3181818 6 4.3181818v-18c0-.51283584-.3860402-.93550716-.8833789-.99327227z'/%3E%3Cpath d='m8.75 6v2.25h2.25v1.5h-2.25v2.25h-1.5v-2.25h-2.25v-1.5h2.25v-2.25z' fill-rule='nonzero'/%3E%3C/g%3E%3C/svg%3E"), pointer;
16
16
  position: relative;
17
17
  }
18
18
  button > * {
@@ -40,6 +40,7 @@ export default class BookmarkButton extends LitElement {
40
40
  constructor() {
41
41
  super();
42
42
  this.state = 'hollow';
43
+ this.side = undefined;
43
44
  }
44
45
 
45
46
  handleClick(e) {
@@ -10,18 +10,32 @@ import { IAIconBookmark } from '@internetarchive/icon-bookmark';
10
10
  customElements.define('icon-bookmark', IAIconBookmark);
11
11
 
12
12
  export default class BookmarksProvider {
13
- constructor(options, bookreader) {
14
- const boundOptions = Object.assign(this, options, {loginClicked: this.bookmarksLoginClicked});
13
+ constructor(options) {
14
+ const {
15
+ baseHost,
16
+ signedIn,
17
+ bookreader,
18
+ modal,
19
+ onProviderChange
20
+ } = options;
21
+
22
+ const referrerStr = `referer=${encodeURIComponent(location.href)}`;
23
+ const loginUrl = `https://${baseHost}/account/login?${referrerStr}`;
24
+
15
25
  this.component = document.createElement('ia-bookmarks');
16
26
  this.component.bookreader = bookreader;
17
- this.component.options = boundOptions;
18
- this.component.displayMode = this.component.options.displayMode;
19
-
27
+ this.component.displayMode = signedIn ? 'bookmarks' : 'login';
28
+ this.component.modal = modal;
29
+ this.component.loginOptions = {
30
+ loginClicked: this.bookmarksLoginClicked,
31
+ loginUrl
32
+ };
20
33
  this.bindEvents();
21
34
 
22
35
  this.icon = html`<icon-bookmark state="hollow" style="--iconWidth: 16px; --iconHeight: 24px;"></icon-bookmark>`;
23
36
  this.label = 'Bookmarks';
24
37
  this.id = 'bookmarks';
38
+ this.onProviderChange = onProviderChange;
25
39
  this.component.setup();
26
40
  this.updateMenu(this.component.bookmarks.length);
27
41
  }
@@ -32,14 +46,12 @@ export default class BookmarksProvider {
32
46
 
33
47
  bindEvents() {
34
48
  this.component.addEventListener('bookmarksChanged', this.bookmarksChanged.bind(this));
35
- this.component.addEventListener('showItemNavigatorModal', this.showItemNavigatorModal);
36
- this.component.addEventListener('closeItemNavigatorModal', this.closeItemNavigatorModal);
37
49
  }
38
50
 
39
51
  bookmarksChanged({ detail }) {
40
52
  const bookmarksLength = Object.keys(detail.bookmarks).length;
41
53
  this.updateMenu(bookmarksLength);
42
- this.onBookmarksChanged(detail.bookmarks);
54
+ this.onProviderChange(detail.bookmarks, detail.showSidePanel);
43
55
  }
44
56
 
45
57
  bookmarksLoginClicked() {