@internetarchive/bookreader 5.0.0-26 → 5.0.0-29

Sign up to get free protection for your applications and to get access to all the features.
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() {