react-native-simple-epub-reader 0.1.0

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 (90) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +37 -0
  3. package/lib/module/components/GestureHandler/index.js +39 -0
  4. package/lib/module/components/GestureHandler/index.js.map +1 -0
  5. package/lib/module/components/Reader.js +207 -0
  6. package/lib/module/components/Reader.js.map +1 -0
  7. package/lib/module/constants/template.js +402 -0
  8. package/lib/module/constants/template.js.map +1 -0
  9. package/lib/module/constants/theme.js +43 -0
  10. package/lib/module/constants/theme.js.map +1 -0
  11. package/lib/module/context/ReaderContext.js +213 -0
  12. package/lib/module/context/ReaderContext.js.map +1 -0
  13. package/lib/module/context/types.js +4 -0
  14. package/lib/module/context/types.js.map +1 -0
  15. package/lib/module/helpers/downloadEpub.js +17 -0
  16. package/lib/module/helpers/downloadEpub.js.map +1 -0
  17. package/lib/module/helpers/loadScripts.js +21 -0
  18. package/lib/module/helpers/loadScripts.js.map +1 -0
  19. package/lib/module/helpers/saveTemplateToFile.js +21 -0
  20. package/lib/module/helpers/saveTemplateToFile.js.map +1 -0
  21. package/lib/module/helpers/webViewInjectFunctions.js +14 -0
  22. package/lib/module/helpers/webViewInjectFunctions.js.map +1 -0
  23. package/lib/module/hooks/useInjectWebviewVariables.js +20 -0
  24. package/lib/module/hooks/useInjectWebviewVariables.js.map +1 -0
  25. package/lib/module/hooks/useReaderState.js +117 -0
  26. package/lib/module/hooks/useReaderState.js.map +1 -0
  27. package/lib/module/index.js +6 -0
  28. package/lib/module/index.js.map +1 -0
  29. package/lib/module/package.json +1 -0
  30. package/lib/module/scripts/epub.js +14326 -0
  31. package/lib/module/scripts/epub.js.map +1 -0
  32. package/lib/module/scripts/jszip.js +6646 -0
  33. package/lib/module/scripts/jszip.js.map +1 -0
  34. package/lib/module/types/index.js +14 -0
  35. package/lib/module/types/index.js.map +1 -0
  36. package/lib/module/types/state.types.js +23 -0
  37. package/lib/module/types/state.types.js.map +1 -0
  38. package/lib/typescript/package.json +1 -0
  39. package/lib/typescript/src/components/GestureHandler/index.d.ts +5 -0
  40. package/lib/typescript/src/components/GestureHandler/index.d.ts.map +1 -0
  41. package/lib/typescript/src/components/Reader.d.ts +4 -0
  42. package/lib/typescript/src/components/Reader.d.ts.map +1 -0
  43. package/lib/typescript/src/constants/template.d.ts +3 -0
  44. package/lib/typescript/src/constants/template.d.ts.map +1 -0
  45. package/lib/typescript/src/constants/theme.d.ts +3 -0
  46. package/lib/typescript/src/constants/theme.d.ts.map +1 -0
  47. package/lib/typescript/src/context/ReaderContext.d.ts +7 -0
  48. package/lib/typescript/src/context/ReaderContext.d.ts.map +1 -0
  49. package/lib/typescript/src/context/types.d.ts +60 -0
  50. package/lib/typescript/src/context/types.d.ts.map +1 -0
  51. package/lib/typescript/src/helpers/downloadEpub.d.ts +2 -0
  52. package/lib/typescript/src/helpers/downloadEpub.d.ts.map +1 -0
  53. package/lib/typescript/src/helpers/loadScripts.d.ts +2 -0
  54. package/lib/typescript/src/helpers/loadScripts.d.ts.map +1 -0
  55. package/lib/typescript/src/helpers/saveTemplateToFile.d.ts +3 -0
  56. package/lib/typescript/src/helpers/saveTemplateToFile.d.ts.map +1 -0
  57. package/lib/typescript/src/helpers/webViewInjectFunctions.d.ts +4 -0
  58. package/lib/typescript/src/helpers/webViewInjectFunctions.d.ts.map +1 -0
  59. package/lib/typescript/src/hooks/useInjectWebviewVariables.d.ts +12 -0
  60. package/lib/typescript/src/hooks/useInjectWebviewVariables.d.ts.map +1 -0
  61. package/lib/typescript/src/hooks/useReaderState.d.ts +6 -0
  62. package/lib/typescript/src/hooks/useReaderState.d.ts.map +1 -0
  63. package/lib/typescript/src/index.d.ts +4 -0
  64. package/lib/typescript/src/index.d.ts.map +1 -0
  65. package/lib/typescript/src/scripts/epub.d.ts +3 -0
  66. package/lib/typescript/src/scripts/epub.d.ts.map +1 -0
  67. package/lib/typescript/src/scripts/jszip.d.ts +3 -0
  68. package/lib/typescript/src/scripts/jszip.d.ts.map +1 -0
  69. package/lib/typescript/src/types/index.d.ts +72 -0
  70. package/lib/typescript/src/types/index.d.ts.map +1 -0
  71. package/lib/typescript/src/types/state.types.d.ts +83 -0
  72. package/lib/typescript/src/types/state.types.d.ts.map +1 -0
  73. package/package.json +171 -0
  74. package/src/components/GestureHandler/index.tsx +63 -0
  75. package/src/components/Reader.tsx +262 -0
  76. package/src/constants/template.ts +399 -0
  77. package/src/constants/theme.ts +42 -0
  78. package/src/context/ReaderContext.tsx +265 -0
  79. package/src/context/types.ts +62 -0
  80. package/src/helpers/downloadEpub.ts +21 -0
  81. package/src/helpers/loadScripts.ts +22 -0
  82. package/src/helpers/saveTemplateToFile.ts +20 -0
  83. package/src/helpers/webViewInjectFunctions.ts +18 -0
  84. package/src/hooks/useInjectWebviewVariables.ts +51 -0
  85. package/src/hooks/useReaderState.ts +118 -0
  86. package/src/index.tsx +3 -0
  87. package/src/scripts/epub.ts +14323 -0
  88. package/src/scripts/jszip.ts +6643 -0
  89. package/src/types/index.ts +83 -0
  90. package/src/types/state.types.ts +86 -0
@@ -0,0 +1,399 @@
1
+ export default `
2
+ <!DOCTYPE html>
3
+ <html>
4
+ <head>
5
+ <meta charset="utf-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>EPUB.js</title>
8
+ <script id="jszip"></script>
9
+ <script id="epubjs"></script>
10
+
11
+ <style type="text/css">
12
+ body {
13
+ margin: 0;
14
+ background-color: #211F26;
15
+ }
16
+
17
+ #viewer {
18
+ height: 100vh;
19
+ width: 100vw;
20
+ overflow: hidden !important;
21
+ display: flex;
22
+ justify-content: center;
23
+ align-items: center;
24
+ }
25
+
26
+ [ref="epubjs-mk-balloon"] {
27
+ background: url("data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPScxLjEnIHhtbG5zPSdodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZycgeG1sbnM6eGxpbms9J2h0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsnIHg9JzBweCcgeT0nMHB4JyB2aWV3Qm94PScwIDAgNzUgNzUnPjxnIGZpbGw9JyNCREJEQkQnIGlkPSdidWJibGUnPjxwYXRoIGNsYXNzPSdzdDAnIGQ9J00zNy41LDkuNEMxOS42LDkuNCw1LDIwLjUsNSwzNC4zYzAsNS45LDIuNywxMS4zLDcuMSwxNS42TDkuNiw2NS42bDE5LTcuM2MyLjgsMC42LDUuOCwwLjksOC45LDAuOSBDNTUuNSw1OS4yLDcwLDQ4LjEsNzAsMzQuM0M3MCwyMC41LDU1LjQsOS40LDM3LjUsOS40eicvPjwvZz48L3N2Zz4=") no-repeat;
28
+ width: 20px;
29
+ height: 20px;
30
+ cursor: pointer;
31
+ margin-left: 0;
32
+ }
33
+
34
+ [ref="epubjs-mk-heart"] {
35
+ background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAACXBIWXMAAAsTAAALEwEAmpwYAAACOUlEQVR4nLWUTWgTURDH14Oe9JiPNqFNujvvzdsm3bdvPxKMFUEPag/iwdaD3j1JDymlCMXiqUeRHvWgFRQUxKPirUU8eFARvCnUj7QXP7DiJtk8easJjRjzIQ784bEz82Pe7MzTtP9tpmnu8UbNpOM4uzvFKF+3GM1BHHIAbwjA7xyY5AaGPuCarZtHmzGcsGM+YevKp2JUrAN4XeW2wSxKMy6wrSkKtbsiJZ96SfnAGZbl8bG6DawhdLwqAK9xYI25XLaufCrmjkjJKQpVF3DLzrDRFtAHXJ9hUNsoxOTH8hn5afGcrBRjkR66w3I/0GoJaPWRO9T63tRGISanmVHzgK1FMBvGmSr/iZeUn5fL8svlRbl5aKQt6bGXjPQ7bKefA5MOIahZOpsuAQmUY3t1pWNSN5WABtwwT2kW4Mki0OqgoMov+YA1rrMTmk3IhCr3hd/5St303EtEV54Yw5xq4y4PcHOFt/etH12xRqQHWFGsn/MFuHAQaPCmGO8b9roQl5OEBpaB862xoZTuc4F+uJDLhv0CF/LZ0DPoe9M097YNNwd2hAMLb9rpnmGrdlr1LrQJO/zH9bMMnBWA4X0n1RV2T6TU6oUc2Pm/vQ0aN/CSAKzfFp0rvWWnI5gNbEnrxWwD59UOL+UzjXc7ftTbYlxezGca0X4Dm+sJ1jQO7LgA/Hoa9eCln5Cv/IQ8i3ogAL+pZdAGMYcQdAGfHSAkmCQkUOc8pXQgWNPUgysAl5XU+Z9gg9gPaBjV+CGbZVoAAAAASUVORK5CYII=") no-repeat;
36
+ width: 20px;
37
+ height: 20px;
38
+ cursor: pointer;
39
+ margin-left: 0;
40
+ }
41
+ </style>
42
+ </head>
43
+
44
+ <body oncopy='return false' oncut='return false'>
45
+ <div id="viewer"></div>
46
+
47
+ <script>
48
+ let book;
49
+ let rendition;
50
+
51
+ const type = window.type;
52
+ const file = window.book;
53
+ const theme = window.theme;
54
+ const initialLocations = window.locations;
55
+ const enableSelection = window.enable_selection;
56
+ const allowScriptedContent = window.allowScriptedContent || false;
57
+ const allowPopups = window.allowPopups || false;
58
+
59
+ if (!file) {
60
+ const reactNativeWebview = window.ReactNativeWebView !== undefined && window.ReactNativeWebView !== null ? window.ReactNativeWebView : window;
61
+ reactNativeWebview.postMessage(JSON.stringify({
62
+ type: "onDisplayError",
63
+ reason: "Book file is missing"
64
+ }));
65
+ }
66
+
67
+ if (type === 'epub' || type === 'opf' || type === 'binary') {
68
+ book = ePub(file);
69
+ } else if (type === 'base64') {
70
+ book = ePub(file, { encoding: "base64" });
71
+ } else {
72
+ const reactNativeWebview = window.ReactNativeWebView !== undefined && window.ReactNativeWebView !== null ? window.ReactNativeWebView : window;
73
+ reactNativeWebview.postMessage(JSON.stringify({
74
+ type: "onDisplayError",
75
+ reason: "Missing or invalid file type"
76
+ }));
77
+ }
78
+
79
+ rendition = book.renderTo("viewer", {
80
+ width: "100%",
81
+ height: "100%",
82
+ manager: "default",
83
+ flow: "auto",
84
+ snap: undefined,
85
+ spread: undefined,
86
+ fullsize: undefined,
87
+ allowPopups: allowPopups,
88
+ allowScriptedContent: allowScriptedContent
89
+ });
90
+
91
+ const reactNativeWebview = window.ReactNativeWebView !== undefined && window.ReactNativeWebView!== null ? window.ReactNativeWebView: window;
92
+ reactNativeWebview.postMessage(JSON.stringify({ type: "onStarted" }));
93
+
94
+ function flatten(chapters) {
95
+ return [].concat.apply([], chapters.map((chapter) => [].concat.apply([chapter], flatten(chapter.subitems))));
96
+ }
97
+
98
+ function getCfiFromHref(book, href) {
99
+ const [_, id] = href.split('#')
100
+ let section = book.spine.get(href.split('/')[1]) || book.spine.get(href) || book.spine.get(href.split('/').slice(1).join('/'))
101
+
102
+ const el = (id ? section.document.getElementById(id) : section.document.body)
103
+ return section.cfiFromElement(el)
104
+ }
105
+
106
+ function getChapter(location) {
107
+ const locationHref = location.start.href
108
+
109
+ let match = flatten(book.navigation.toc)
110
+ .filter((chapter) => {
111
+ return book.canonical(chapter.href).includes(locationHref)
112
+ }, null)
113
+ .reduce((result, chapter) => {
114
+ const locationAfterChapter = ePub.CFI.prototype.compare(location.start.cfi, getCfiFromHref(book, chapter.href)) > 0
115
+ return locationAfterChapter ? chapter : result
116
+ }, null);
117
+
118
+ return match;
119
+ };
120
+
121
+ const makeRangeCfi = (a, b) => {
122
+ const CFI = new ePub.CFI()
123
+ const start = CFI.parse(a), end = CFI.parse(b)
124
+ const cfi = {
125
+ range: true,
126
+ base: start.base,
127
+ path: {
128
+ steps: [],
129
+ terminal: null
130
+ },
131
+ start: start.path,
132
+ end: end.path
133
+ }
134
+ const len = cfi.start.steps.length
135
+ for (let i = 0; i < len; i++) {
136
+ if (CFI.equalStep(cfi.start.steps[i], cfi.end.steps[i])) {
137
+ if (i == len - 1) {
138
+ // Last step is equal, check terminals
139
+ if (cfi.start.terminal === cfi.end.terminal) {
140
+ // CFI's are equal
141
+ cfi.path.steps.push(cfi.start.steps[i])
142
+ // Not a range
143
+ cfi.range = false
144
+ }
145
+ } else cfi.path.steps.push(cfi.start.steps[i])
146
+ } else break
147
+ }
148
+ cfi.start.steps = cfi.start.steps.slice(cfi.path.steps.length)
149
+ cfi.end.steps = cfi.end.steps.slice(cfi.path.steps.length)
150
+
151
+ return 'epubcfi(' + CFI.segmentString(cfi.base)
152
+ + '!' + CFI.segmentString(cfi.path)
153
+ + ',' + CFI.segmentString(cfi.start)
154
+ + ',' + CFI.segmentString(cfi.end)
155
+ + ')'
156
+ }
157
+
158
+ if (!enableSelection) {
159
+ rendition.themes.default({
160
+ 'body': {
161
+ '-webkit-touch-callout': 'none', /* iOS Safari */
162
+ '-webkit-user-select': 'none', /* Safari */
163
+ '-khtml-user-select': 'none', /* Konqueror HTML */
164
+ '-moz-user-select': 'none', /* Firefox */
165
+ '-ms-user-select': 'none', /* Internet Explorer/Edge */
166
+ 'user-select': 'none'
167
+ }
168
+ });
169
+ }
170
+
171
+ book.ready
172
+ .then(function () {
173
+ if (initialLocations) {
174
+ book.locations.load(initialLocations);
175
+ }
176
+ return rendition.display();
177
+ })
178
+ .then(function () {
179
+ var currentLocation = rendition.currentLocation();
180
+
181
+ reactNativeWebview.postMessage(JSON.stringify({
182
+ type: "onReady",
183
+ totalLocations: book.locations.total,
184
+ currentLocation: currentLocation,
185
+ progress: currentLocation?.start?.cfi
186
+ ? book.locations.percentageFromCfi(currentLocation.start.cfi)
187
+ : 0,
188
+ }));
189
+
190
+ if (initialLocations) {
191
+ reactNativeWebview.postMessage(JSON.stringify({
192
+ type: "onLocationsReady",
193
+ epubKey: book.key(),
194
+ locations: initialLocations,
195
+ totalLocations: book.locations.total,
196
+ currentLocation: currentLocation,
197
+ progress: currentLocation?.start?.cfi
198
+ ? book.locations.percentageFromCfi(currentLocation.start.cfi)
199
+ : 0,
200
+ }));
201
+ return Promise.resolve();
202
+ }
203
+
204
+ return book.locations.generate(1600).then(function () {
205
+ var generatedLocation = rendition.currentLocation() || currentLocation;
206
+ reactNativeWebview.postMessage(JSON.stringify({
207
+ type: "onLocationsReady",
208
+ epubKey: book.key(),
209
+ locations: book.locations.save(),
210
+ totalLocations: book.locations.total,
211
+ currentLocation: generatedLocation,
212
+ progress: generatedLocation?.start?.cfi
213
+ ? book.locations.percentageFromCfi(generatedLocation.start.cfi)
214
+ : 0,
215
+ }));
216
+ }).catch(function () {
217
+ reactNativeWebview.postMessage(JSON.stringify({
218
+ type: "onLocationsReady",
219
+ epubKey: book.key(),
220
+ locations: [],
221
+ totalLocations: book.locations.total,
222
+ currentLocation: currentLocation,
223
+ progress: 0,
224
+ }));
225
+ });
226
+
227
+ book
228
+ .coverUrl()
229
+ .then(async (url) => {
230
+ var reader = new FileReader();
231
+ reader.onload = (res) => {
232
+ reactNativeWebview.postMessage(
233
+ JSON.stringify({
234
+ type: "meta",
235
+ metadata: {
236
+ cover: reader.result,
237
+ author: book.package.metadata.creator,
238
+ title: book.package.metadata.title,
239
+ description: book.package.metadata.description,
240
+ language: book.package.metadata.language,
241
+ publisher: book.package.metadata.publisher,
242
+ rights: book.package.metadata.rights,
243
+ },
244
+ })
245
+ );
246
+ };
247
+ reader.readAsDataURL(await fetch(url).then((res) => res.blob()));
248
+ })
249
+ .catch(() => {
250
+ reactNativeWebview.postMessage(
251
+ JSON.stringify({
252
+ type: "meta",
253
+ metadata: {
254
+ cover: undefined,
255
+ author: book.package.metadata.creator,
256
+ title: book.package.metadata.title,
257
+ description: book.package.metadata.description,
258
+ language: book.package.metadata.language,
259
+ publisher: book.package.metadata.publisher,
260
+ rights: book.package.metadata.rights,
261
+ },
262
+ })
263
+ );
264
+ });
265
+
266
+ book.loaded.navigation.then(function (item) {
267
+ reactNativeWebview.postMessage(JSON.stringify({
268
+ type: 'onNavigationLoaded',
269
+ toc: item.toc,
270
+ landmarks: item.landmarks
271
+ }));
272
+ });
273
+ })
274
+ .catch(function (err) {
275
+ reactNativeWebview.postMessage(JSON.stringify({
276
+ type: "onDisplayError",
277
+ reason: err.message || err.toString()
278
+ }));
279
+ });
280
+
281
+ let isAnimating = false;
282
+ const originalNext = rendition.next.bind(rendition);
283
+ const originalPrev = rendition.prev.bind(rendition);
284
+
285
+ rendition.next = function() {
286
+ if (isAnimating) return;
287
+ isAnimating = true;
288
+
289
+ const container = rendition.manager.container;
290
+ container.style.transition = 'opacity 0.2s ease-out';
291
+ container.style.opacity = '0.4';
292
+
293
+ setTimeout(() => {
294
+ originalNext();
295
+ setTimeout(() => {
296
+ container.style.opacity = '1';
297
+ setTimeout(() => {
298
+ container.style.transition = '';
299
+ isAnimating = false;
300
+ }, 200);
301
+ }, 50);
302
+ }, 100);
303
+ };
304
+
305
+ rendition.prev = function() {
306
+ if (isAnimating) return;
307
+ isAnimating = true;
308
+
309
+ const container = rendition.manager.container;
310
+ container.style.transition = 'opacity 0.2s ease-out';
311
+ container.style.opacity = '0.4';
312
+
313
+ setTimeout(() => {
314
+ originalPrev();
315
+ setTimeout(() => {
316
+ container.style.opacity = '1';
317
+ setTimeout(() => {
318
+ container.style.transition = '';
319
+ isAnimating = false;
320
+ }, 200);
321
+ }, 50);
322
+ }, 100);
323
+ };
324
+
325
+ rendition.on('started', () => {
326
+ rendition.themes.register({ theme: theme });
327
+ rendition.themes.select('theme');
328
+ });
329
+
330
+ rendition.on("relocated", function (location) {
331
+ var percent = book.locations.percentageFromCfi(location.start.cfi);
332
+ var percentage = Math.floor(percent * 100);
333
+ var chapter = getChapter(location);
334
+
335
+ reactNativeWebview.postMessage(JSON.stringify({
336
+ type: "onLocationChange",
337
+ totalLocations: book.locations.total,
338
+ currentLocation: location,
339
+ progress: percentage,
340
+ currentSection: chapter,
341
+ }));
342
+
343
+ if (location.atStart) {
344
+ reactNativeWebview.postMessage(JSON.stringify({
345
+ type: "onBeginning",
346
+ }));
347
+ }
348
+
349
+ if (location.atEnd) {
350
+ reactNativeWebview.postMessage(JSON.stringify({
351
+ type: "onFinish",
352
+ }));
353
+ }
354
+ });
355
+
356
+ rendition.on("orientationchange", function (orientation) {
357
+ reactNativeWebview.postMessage(JSON.stringify({
358
+ type: 'onOrientationChange',
359
+ orientation: orientation
360
+ }));
361
+ });
362
+
363
+ rendition.on("rendered", function (section) {
364
+ reactNativeWebview.postMessage(JSON.stringify({
365
+ type: 'onRendered',
366
+ section: section,
367
+ currentSection: book.navigation.get(section.href),
368
+ }));
369
+ });
370
+
371
+ rendition.on("layout", function (layout) {
372
+ reactNativeWebview.postMessage(JSON.stringify({
373
+ type: 'onLayout',
374
+ layout: layout,
375
+ }));
376
+ });
377
+
378
+ rendition.on("selected", function (cfiRange, contents) {
379
+ book.getRange(cfiRange).then(function (range) {
380
+ if (range) {
381
+ reactNativeWebview.postMessage(JSON.stringify({
382
+ type: 'onSelected',
383
+ cfiRange: cfiRange,
384
+ text: range.toString(),
385
+ }));
386
+ }
387
+ });
388
+ });
389
+
390
+ rendition.on("resized", function (layout) {
391
+ reactNativeWebview.postMessage(JSON.stringify({
392
+ type: 'onResized',
393
+ layout: layout,
394
+ }));
395
+ });
396
+ </script>
397
+ </body>
398
+ </html>
399
+ `;
@@ -0,0 +1,42 @@
1
+ import type { Theme } from '../types';
2
+
3
+ export const defaultTheme: Theme = {
4
+ 'body': {
5
+ background: '#FFF',
6
+ },
7
+ 'span': {
8
+ color: '#000',
9
+ },
10
+ 'p': {
11
+ color: '#000',
12
+ },
13
+ 'li': {
14
+ color: '#000',
15
+ },
16
+ 'h1': {
17
+ color: '#000',
18
+ },
19
+ 'h2': {
20
+ color: '#000',
21
+ },
22
+ 'h3': {
23
+ color: '#000',
24
+ },
25
+ 'h4': {
26
+ color: '#000',
27
+ },
28
+ 'h5': {
29
+ color: '#000',
30
+ },
31
+ 'h6': {
32
+ color: '#000',
33
+ },
34
+ 'a': {
35
+ 'color': '#000',
36
+ 'pointer-events': 'auto',
37
+ 'cursor': 'pointer',
38
+ },
39
+ '::selection': {
40
+ background: 'lightskyblue',
41
+ },
42
+ };
@@ -0,0 +1,265 @@
1
+ import {
2
+ createContext,
3
+ useCallback,
4
+ useMemo,
5
+ useReducer,
6
+ useRef,
7
+ useState,
8
+ } from 'react';
9
+ import type { ReaderContextProps } from './types';
10
+ import { defaultTheme } from '../constants/theme';
11
+ import type WebView from 'react-native-webview';
12
+ import * as webViewInjectFunctions from '../helpers/webViewInjectFunctions';
13
+ import type { ePubCfi, Flow, Location, Theme } from '../types';
14
+ import { useReaderState } from '../hooks/useReaderState';
15
+ import { Actions } from '../types/state.types';
16
+
17
+ const ReaderContext = createContext<ReaderContextProps>({
18
+ registerBook: () => {},
19
+ setAtStart: () => {},
20
+ setAtEnd: () => {},
21
+ setTotalLocations: () => {},
22
+ setCurrentLocation: () => {},
23
+ setMeta: () => {},
24
+ setProgress: () => {},
25
+ setLocations: () => {},
26
+
27
+ goToLocation: () => {},
28
+ goPrevious: () => {},
29
+ goNext: () => {},
30
+ getLocations: () => [],
31
+ getCurrentLocation: () => null,
32
+ getMeta: () => ({
33
+ cover: '',
34
+ author: '',
35
+ title: '',
36
+ description: '',
37
+ language: '',
38
+ publisher: '',
39
+ rights: '',
40
+ }),
41
+
42
+ atStart: false,
43
+ atEnd: false,
44
+ totalLocations: 0,
45
+ currentLocation: null,
46
+ meta: {
47
+ cover: '',
48
+ author: '',
49
+ title: '',
50
+ description: '',
51
+ language: '',
52
+ publisher: '',
53
+ rights: '',
54
+ },
55
+ progress: 0,
56
+ locations: [],
57
+ theme: defaultTheme,
58
+
59
+ injectJavascript: () => {},
60
+ changeFontSize: () => {},
61
+ changeTheme: () => {},
62
+ fontSize: '9pt',
63
+
64
+ isLoading: false,
65
+ setIsLoading: () => {},
66
+ });
67
+
68
+ function ReaderProvider({ children }: { children: React.ReactNode }) {
69
+ const { bookReducer, initialState } = useReaderState();
70
+ const [state, dispatch] = useReducer(bookReducer, initialState);
71
+ const [loading, setLoading] = useState<boolean>(true);
72
+ const book = useRef<WebView | null>(null);
73
+
74
+ const registerBook = useCallback((bookRef: WebView) => {
75
+ book.current = bookRef;
76
+ }, []);
77
+
78
+ const setAtStart = useCallback((atStart: boolean) => {
79
+ dispatch({ type: Actions.SET_AT_START, payload: atStart });
80
+ }, []);
81
+ const setAtEnd = useCallback((atEnd: boolean) => {
82
+ dispatch({ type: Actions.SET_AT_END, payload: atEnd });
83
+ }, []);
84
+
85
+ const setTotalLocations = useCallback((totalLocations: number) => {
86
+ dispatch({ type: Actions.SET_TOTAL_LOCATIONS, payload: totalLocations });
87
+ }, []);
88
+
89
+ const setCurrentLocation = useCallback((location: Location) => {
90
+ dispatch({ type: Actions.SET_CURRENT_LOCATION, payload: location });
91
+ }, []);
92
+
93
+ const setMeta = useCallback(
94
+ (meta: {
95
+ cover: string | ArrayBuffer | null | undefined;
96
+ author: string;
97
+ title: string;
98
+ description: string;
99
+ language: string;
100
+ publisher: string;
101
+ rights: string;
102
+ }) => {
103
+ dispatch({ type: Actions.SET_META, payload: meta });
104
+ },
105
+ []
106
+ );
107
+
108
+ const setProgress = useCallback((progress: number) => {
109
+ dispatch({ type: Actions.SET_PROGRESS, payload: progress });
110
+ }, []);
111
+
112
+ const setLocations = useCallback((locations: ePubCfi[]) => {
113
+ dispatch({ type: Actions.SET_LOCATIONS, payload: locations });
114
+ }, []);
115
+
116
+ const getLocations = useCallback(() => state.locations, [state.locations]);
117
+
118
+ const getCurrentLocation = useCallback(
119
+ () => state.currentLocation,
120
+ [state.currentLocation]
121
+ );
122
+
123
+ const getMeta = useCallback(() => state.meta, [state.meta]);
124
+ const changeFontFamily = useCallback((fontFamily: string) => {
125
+ book.current?.injectJavaScript(`
126
+ rendition.themes.font('${fontFamily}');
127
+ rendition.views().forEach(view => view.pane ? view.pane.render() : null); true;
128
+ `);
129
+ dispatch({ type: Actions.CHANGE_FONT_FAMILY, payload: fontFamily });
130
+ }, []);
131
+
132
+ const injectJavascript = useCallback((script: string) => {
133
+ book.current?.injectJavaScript(script);
134
+ }, []);
135
+
136
+ const changeFlow = useCallback((flow: Flow) => {
137
+ webViewInjectFunctions.injectJavaScript(
138
+ book,
139
+ `rendition.flow(${JSON.stringify(flow)}); true`
140
+ );
141
+ dispatch({ type: Actions.SET_FLOW, payload: flow });
142
+ }, []);
143
+
144
+ const setFlow = useCallback((flow: Flow) => {
145
+ dispatch({ type: Actions.SET_FLOW, payload: flow });
146
+ }, []);
147
+
148
+ const changeTheme = useCallback((theme: Theme) => {
149
+ book.current?.injectJavaScript(`
150
+ rendition.themes.register({ theme: ${JSON.stringify(theme)} });
151
+ rendition.themes.select('theme');
152
+ rendition.views().forEach(view => view.pane ? view.pane.render() : null); true;
153
+ `);
154
+ dispatch({ type: Actions.CHANGE_THEME, payload: theme });
155
+ }, []);
156
+
157
+ const goNext = useCallback(() => {
158
+ webViewInjectFunctions.injectJavaScript(
159
+ book,
160
+ `
161
+ rendition.next();
162
+ `
163
+ );
164
+ }, []);
165
+
166
+ const goPrevious = useCallback(() => {
167
+ webViewInjectFunctions.injectJavaScript(
168
+ book,
169
+ `
170
+ rendition.prev();
171
+ `
172
+ );
173
+ }, []);
174
+
175
+ const goToLocation = useCallback((targetCfi: ePubCfi) => {
176
+ book.current?.injectJavaScript(`rendition.display('${targetCfi}'); true`);
177
+ }, []);
178
+
179
+ const changeFontSize = useCallback((size: string) => {
180
+ book.current?.injectJavaScript(`
181
+ rendition.themes.fontSize('${size}');
182
+ rendition.views().forEach(view => view.pane ? view.pane.render() : null); true;
183
+ `);
184
+ dispatch({ type: Actions.CHANGE_FONT_SIZE, payload: size });
185
+ }, []);
186
+
187
+ const contextValue = useMemo(
188
+ () => ({
189
+ registerBook,
190
+ goToLocation,
191
+ goNext,
192
+ goPrevious,
193
+ setAtStart,
194
+ setAtEnd,
195
+ setTotalLocations,
196
+ setCurrentLocation,
197
+ changeTheme,
198
+ getLocations,
199
+ getCurrentLocation,
200
+ getMeta,
201
+ setMeta,
202
+ setProgress,
203
+ setLocations,
204
+ changeFontFamily,
205
+ injectJavascript,
206
+ changeFlow,
207
+ setFlow,
208
+ changeFontSize,
209
+ theme: state.theme,
210
+ flow: state.flow,
211
+ fontSize: state.fontSize,
212
+ atStart: state.atStart,
213
+ atEnd: state.atEnd,
214
+ totalLocations: state.totalLocations,
215
+ currentLocation: state.currentLocation,
216
+ meta: state.meta,
217
+ progress: state.progress,
218
+ locations: state.locations,
219
+ isLoading: loading,
220
+ setIsLoading: setLoading,
221
+ }),
222
+ [
223
+ registerBook,
224
+ goNext,
225
+ goPrevious,
226
+ goToLocation,
227
+ setAtStart,
228
+ setAtEnd,
229
+ setTotalLocations,
230
+ setCurrentLocation,
231
+ changeTheme,
232
+ getLocations,
233
+ getCurrentLocation,
234
+ getMeta,
235
+ setMeta,
236
+ setProgress,
237
+ setLocations,
238
+ changeFontFamily,
239
+ injectJavascript,
240
+ changeFlow,
241
+ setFlow,
242
+ changeFontSize,
243
+ state.theme,
244
+ state.flow,
245
+ state.fontSize,
246
+ state.atStart,
247
+ state.atEnd,
248
+ state.totalLocations,
249
+ state.currentLocation,
250
+ state.meta,
251
+ state.progress,
252
+ state.locations,
253
+ loading,
254
+ setLoading,
255
+ ]
256
+ );
257
+
258
+ return (
259
+ <ReaderContext.Provider value={contextValue}>
260
+ {children}
261
+ </ReaderContext.Provider>
262
+ );
263
+ }
264
+
265
+ export { ReaderContext, ReaderProvider };