@internetarchive/ia-book-actions 1.1.0-alpha8 → 1.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.
@@ -0,0 +1,26 @@
1
+ name: App CI
2
+
3
+ on:
4
+ push:
5
+ branches: [ main ]
6
+ pull_request:
7
+ branches: [ main ]
8
+
9
+ jobs:
10
+ build:
11
+ runs-on: ubuntu-latest
12
+
13
+ steps:
14
+ - uses: actions/checkout@v4
15
+ - uses: actions/setup-node@v3
16
+ with:
17
+ node-version: 16.x
18
+
19
+ - name: Install dependencies
20
+ run: yarn install
21
+
22
+ # - name: Run tests
23
+ # run: yarn run test
24
+
25
+ # - name: Upload reports
26
+ # run: npx codecov
@@ -0,0 +1,39 @@
1
+ # This workflow will generate the static page under `main` subdirectory inside the `gh-pages` branch
2
+
3
+ # This workflow will run every time new changes were pushed to the `main` branch
4
+
5
+ name: App build CI/CD to main branch
6
+
7
+ on:
8
+ push:
9
+ branches: [ main ]
10
+
11
+ jobs:
12
+ build:
13
+ runs-on: ubuntu-latest
14
+
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+ with:
18
+ persist-credentials: false
19
+
20
+ - uses: actions/setup-node@v3
21
+ with:
22
+ node-version: 20
23
+
24
+ - name: Install dependencies
25
+ run: yarn install
26
+
27
+ - name: Create build files for gh-pages deploy
28
+ run: yarn prepare:ghpages
29
+
30
+ # Reference: https://github.com/JamesIves/github-pages-deploy-action
31
+ - name: Deploy 🚀
32
+ uses: JamesIves/github-pages-deploy-action@v4.4.3
33
+ with:
34
+ branch: gh-pages
35
+ folder: ghpages
36
+ clean-exclude: pr/
37
+ force: false
38
+ target-folder: main
39
+ token: ${{ secrets.GH_TOKEN }}
@@ -0,0 +1,37 @@
1
+ # This workflow will generate the static page under `pr` subdirectory inside the `gh-pages` branch
2
+
3
+ # This workflow will run every time there's a PR opened, reopened, synchronize, or closed
4
+
5
+ name: Deploy PR previews
6
+
7
+ on:
8
+ pull_request:
9
+ types:
10
+ - opened
11
+ - reopened
12
+ - synchronize
13
+ - closed
14
+
15
+ concurrency: preview-${{ github.ref }}
16
+
17
+ jobs:
18
+ deploy-preview:
19
+ runs-on: ubuntu-20.04
20
+ steps:
21
+ - name: Checkout
22
+ uses: actions/checkout@v4
23
+
24
+ - uses: actions/setup-node@v3
25
+ with:
26
+ node-version: 20.x
27
+
28
+ - name: Install and Build
29
+ run: |
30
+ yarn install
31
+ yarn prepare:ghpages
32
+ # Reference: https://github.com/rossjrw/pr-preview-action
33
+ - name: Deploy preview
34
+ uses: rossjrw/pr-preview-action@v1
35
+ with:
36
+ source-dir: ./ghpages/
37
+ umbrella-dir: pr
package/demo/index.html CHANGED
@@ -76,6 +76,13 @@
76
76
  display: inline-grid;
77
77
  }
78
78
 
79
+ .action-button button {
80
+ padding: 6px;
81
+ cursor: pointer;
82
+ border: 2px solid #194880;
83
+ border-radius: 10px;
84
+ }
85
+
79
86
  body.modal-manager-open {
80
87
  overflow: hidden;
81
88
  }
@@ -128,10 +135,11 @@
128
135
  iaBookActions.modalConfig = modalConfig;
129
136
  iaBookActions.lendingBarPostInit = lendingBarPostInit;
130
137
  iaBookActions.tokenDelay = 2; // 2 seconds
138
+ iaBookActions.timerExecutionSeconds = 4 // 4 seconds
131
139
  iaBookActions.returnUrl = '';
132
140
  iaBookActions.localCache = localCache;
133
141
 
134
- let loanTotalTime2Min = { loanTotalTime: 170, loanRenewAtLast: 80, pageChangedInLast: 15 };
142
+ let loanTotalTime2Min = { loanTotalTime: 120, loanRenewAtLast: 80, pageChangedInLast: 15 };
135
143
  let loanTotalTime5Min = { loanTotalTime: 300, loanRenewAtLast: 180, pageChangedInLast: 15 };
136
144
  let loanTotalTime1Hour = { loanTotalTime: 3600, loanRenewAtLast: 660, pageChangedInLast: 900 };
137
145
 
@@ -286,6 +294,19 @@
286
294
  iaBookActions.browseHasExpired();
287
295
  });
288
296
 
297
+ document.querySelector('#resync_timer').addEventListener('click', async (e) => {
298
+ if (iaBookActions.lendingStatus.user_has_browsed) {
299
+ document.querySelector('ia-book-actions').dispatchEvent(
300
+ new Event('visibilitychange', {
301
+ detail: {},
302
+ bubbles: true,
303
+ composed: true,
304
+ })
305
+ );
306
+ }
307
+ await new Promise(resolve => setTimeout(resolve, 5000));
308
+ });
309
+
289
310
  document.querySelectorAll('.availableToBorrow input[type=radio]').forEach((item) => {
290
311
  item.addEventListener('click', (e) => {
291
312
  if (e.target.value === 'available_to_borrow') {
@@ -359,8 +380,9 @@
359
380
  </td>
360
381
  <td class="searchParam">
361
382
  <label><input type="checkbox" name="search" value="Search Param" />Search Param</label>
383
+ </td>
362
384
  <td>
363
- <button class="pageChangedEvent">Page Change Event</button>
385
+ <label class="ia-button"><button class="pageChangedEvent">Page Change Event</button></label>
364
386
  </td>
365
387
  </tr>
366
388
  </table>
@@ -385,12 +407,6 @@
385
407
  <label><input type="radio" name="browse" value="available_to_browse" />available_to_browse</label>
386
408
  <label><input type="radio" name="browse" value="user_has_browsed" />user_has_browsed</label>
387
409
  <label><input type="radio" name="browse" value="browsingExpired" />browsingExpired</label>
388
- <div>
389
- <button id="show_warning_modal">When Browsing (`user_has_browsed`), show Warning Modal</button>
390
- </div>
391
- <div>
392
- <button id="show_expired_modal">While Browsing (`user_has_browsed`), Expire book & show its modal</button>
393
- </div>
394
410
  </td>
395
411
  <td class="availableToBorrow">
396
412
  <label><input type="radio" name="borrow" value="available_to_borrow" />available_to_borrow</label>
@@ -410,6 +426,26 @@
410
426
  font-weight: bold; display: none" name="purchase" value="Reflect" />
411
427
  </td>
412
428
  </tr>
429
+ <tr>
430
+ <td>
431
+ <fieldset style="margin-top: 10px;">
432
+ <legend style="color: black;">controls for browsed book</legend>
433
+ <table aria-hidden="true">
434
+ <tbody><tr>
435
+ <td class="action-button">
436
+ <button id="show_warning_modal">When Browsing (`user_has_browsed`),<br> show Warning Modal</button>
437
+ </td>
438
+ <td class="action-button">
439
+ <button id="show_expired_modal">While Browsing (`user_has_browsed`),<br> Expire book & show its modal</button>
440
+ </td>
441
+ <td class="action-button">
442
+ <button id="resync_timer">While Browsing (`user_has_browsed`),<br> resync timer</button>
443
+ </td>
444
+ </tr>
445
+ </tbody></table>
446
+ </fieldset>
447
+ </td>
448
+ </tr>
413
449
  </table>
414
450
  </fieldset>
415
451
  </div>
package/index.html ADDED
@@ -0,0 +1,474 @@
1
+ <!doctype html>
2
+ <html lang="en-GB">
3
+
4
+ <head>
5
+ <meta charset="utf-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
7
+ <style>
8
+ html {
9
+ font-size: 10px;
10
+ /* This is to match petabox's base font size */
11
+ }
12
+ body {
13
+ margin: 0;
14
+ text-align: center;
15
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
16
+ }
17
+ #demo {
18
+ height: 100px;
19
+ }
20
+ ia-book-actions {
21
+ --white: #fff;
22
+ --black: #000;
23
+ --primaryBGColor: var(--black);
24
+ --secondaryBGColor: #222;
25
+ --tertiaryBGColor: #333;
26
+ --primaryTextColor: var(--white);
27
+ --primaryCTAFill: #194880;
28
+ --primaryCTABorder: #c5d1df;
29
+ --secondaryCTAFill: #333;
30
+ --secondaryCTABorder: #999;
31
+ --secondaryCTAFillRGB: 51, 51, 51;
32
+ --primaryErrorCTAFill: #e51c26;
33
+ --primaryErrorCTABorder: #f8c6c8;
34
+ --primaryCTAFillRGB: 25, 72, 128;
35
+ --primaryErrorCTAFillRGB: 229, 28, 38;
36
+ --primaryDisableCTAFill: #767676;
37
+ }
38
+
39
+ fieldset {
40
+ width: fit-content;
41
+ margin-top: 50px;
42
+ }
43
+ fieldset legend {
44
+ text-align: left;
45
+ font-size: 12px;
46
+ }
47
+ td {
48
+ color: white;
49
+ font-size: 14px;
50
+ border-radius: 5%;
51
+ display: table-cell;
52
+ text-align: initial;
53
+ margin: 5px 0;
54
+ width: max-content;
55
+ display: block;
56
+ }
57
+ label {
58
+ padding: 10px;
59
+ display: block;
60
+ cursor: pointer;
61
+ margin: 2px;
62
+ background: #4e545a;
63
+ border-radius: 10px;
64
+ }
65
+ input[type="radio"], input[type="checkbox"] {
66
+ margin-top: 0;
67
+ vertical-align: middle;
68
+ }
69
+
70
+ .initial td {
71
+ float: left;
72
+ margin: 2px;
73
+ }
74
+
75
+ fieldset {
76
+ display: inline-grid;
77
+ }
78
+
79
+ .action-button button {
80
+ padding: 6px;
81
+ cursor: pointer;
82
+ border: 2px solid #194880;
83
+ border-radius: 10px;
84
+ }
85
+
86
+ body.modal-manager-open {
87
+ overflow: hidden;
88
+ }
89
+
90
+ modal-manager[mode='closed'] {
91
+ display: none;
92
+ }
93
+ </style>
94
+ </head>
95
+
96
+ <body>
97
+ <ia-book-actions></ia-book-actions>
98
+
99
+ <script type="module">
100
+ /* eslint-disable */
101
+
102
+ import './src/ia-book-actions.js';
103
+ import {
104
+ defaultLendingStatus,
105
+ canBrowseAndBorrow,
106
+ } from './src/assets/data/lending-get-status-service.js';
107
+ import { ModalConfig } from '@internetarchive/modal-manager';
108
+ import { LocalCache } from '@internetarchive/local-cache';
109
+
110
+ const localCache = new LocalCache({
111
+ namespace: 'loanRenew',
112
+ defaultTTL: 1 * 60, // 1 minute
113
+ });
114
+
115
+ const userid = '@neeraj-archive';
116
+ const identifier = 'naturalhistoryof00unse_4111';
117
+ let bwbPurchaseUrl = 'https://www.betterworldbooks.com/product/detail/cambridge-ancient-hist-v04-0521044863';
118
+
119
+
120
+ const modalConfig = new ModalConfig();
121
+ modalConfig.headerColor = '#d9534f';
122
+
123
+ let lendingBarPostInit = function lendingBarPostInit() {
124
+ setTimeout(function() {}, 100);
125
+ }
126
+
127
+ // set defaultLendingStatus for unavailable (without borrowables) book
128
+ let iaBookActions = document.querySelector('ia-book-actions');
129
+
130
+ iaBookActions.userid = userid;
131
+ iaBookActions.identifier = identifier;
132
+ iaBookActions.bookTitle = 'Contemporary Black biography. Volume 39 : profiles from the interContemporary Black biography. Volume 39';
133
+ iaBookActions.lendingStatus = canBrowseAndBorrow;
134
+ iaBookActions.bwbPurchaseUrl = '';
135
+ iaBookActions.modalConfig = modalConfig;
136
+ iaBookActions.lendingBarPostInit = lendingBarPostInit;
137
+ iaBookActions.tokenDelay = 10; // 2 seconds
138
+ iaBookActions.timerExecutionSeconds = 3 // 4 seconds
139
+ iaBookActions.returnUrl = '';
140
+ iaBookActions.localCache = localCache;
141
+
142
+ let loanTotalTime2Min = { loanTotalTime: 120, loanRenewAtLast: 110, pageChangedInLast: 30 };
143
+ let loanTotalTime5Min = { loanTotalTime: 300, loanRenewAtLast: 240, pageChangedInLast: 60 };
144
+ let loanTotalTime10Min = { loanTotalTime: 600, loanRenewAtLast: 480, pageChangedInLast: 120 };
145
+ let loanTotalTime30Min = { loanTotalTime: 1800, loanRenewAtLast: 1500, pageChangedInLast: 600 };
146
+ let loanTotalTime1Hour = { loanTotalTime: 3600, loanRenewAtLast: 660, pageChangedInLast: 900 };
147
+
148
+ iaBookActions.loanRenewTimeConfig = loanTotalTime2Min;
149
+ let params = new URLSearchParams(document.location.search);
150
+ switch (params.get("timer")) {
151
+ case '5':
152
+ iaBookActions.loanRenewTimeConfig = loanTotalTime5Min;
153
+ break;
154
+ case '10':
155
+ iaBookActions.loanRenewTimeConfig = loanTotalTime10Min;
156
+ break;
157
+ case '30':
158
+ iaBookActions.loanRenewTimeConfig = loanTotalTime30Min;
159
+ break;
160
+ case '60':
161
+ iaBookActions.loanRenewTimeConfig = loanTotalTime1Hour;
162
+ break;
163
+ default:
164
+ iaBookActions.loanRenewTimeConfig = loanTotalTime2Min;
165
+ break;
166
+ }
167
+
168
+ let lendingStatus1 = iaBookActions.lendingStatus;
169
+ let secondsLeftOnLoan = iaBookActions.loanRenewTimeConfig.loanTotalTime;
170
+
171
+ document.querySelectorAll('.titleBar input[type=checkbox]').forEach((item) => {
172
+ item.addEventListener('click', (e) => {
173
+ if (e.target.checked) {
174
+ iaBookActions.barType = 'title';
175
+ } else {
176
+ iaBookActions.barType = 'action';
177
+ }
178
+ });
179
+ });
180
+
181
+ document.querySelectorAll('.searchParam input[type=checkbox]').forEach((item) => {
182
+ item.addEventListener('click', (e) => {
183
+ var searchParams = new URLSearchParams(window.location.search);
184
+ var separate = '';
185
+ if (e.target.checked) {
186
+ separate = '?';
187
+ searchParams.set('q', 'test');
188
+ } else {
189
+ separate = '';
190
+ searchParams.delete('q');
191
+ }
192
+
193
+ if (history.pushState) {
194
+ var newurl = window.location.protocol + "//" + window.location.host + window.location.pathname + separate + searchParams;
195
+ window.history.pushState({path:newurl},'',newurl);
196
+ }
197
+ });
198
+ });
199
+
200
+ document.querySelectorAll('.errorEnable input[type=checkbox]').forEach((item) => {
201
+ if (window.location.href.indexOf('?error=true') !== -1) {
202
+ item.checked = true;
203
+ }
204
+ item.addEventListener('click', (e) => {
205
+ var searchParams = new URLSearchParams(window.location.search);
206
+ var separate = '';
207
+ if (e.target.checked) {
208
+ separate = '?';
209
+ searchParams.set('error', true);
210
+ } else {
211
+ separate = '';
212
+ searchParams.delete('error');
213
+ }
214
+
215
+ if (history.pushState) {
216
+ var newurl = window.location.protocol + "//" + window.location.host + window.location.pathname + separate + searchParams;
217
+ window.history.pushState({path:newurl},'',newurl);
218
+ }
219
+ });
220
+ });
221
+
222
+
223
+ document.querySelectorAll('.userState input[type=checkbox]').forEach((item) => {
224
+ item.addEventListener('click', (e) => {
225
+ if (e.target.checked) {
226
+ if (e.target.value === 'isAdmin') lendingStatus1.isAdmin = true;
227
+ if (e.target.value === 'isLoggedIn') iaBookActions.userid = '@neeraj';
228
+ } else {
229
+ if (e.target.value === 'isAdmin') lendingStatus1.isAdmin = false;
230
+ if (e.target.value === 'isLoggedIn') iaBookActions.userid = '';
231
+ }
232
+ let lending = {...defaultLendingStatus, ...lendingStatus1};
233
+ iaBookActions.lendingStatus = lending;
234
+ });
235
+ });
236
+
237
+ document.querySelectorAll('.printDisabled input[type=checkbox]').forEach((item) => {
238
+ item.addEventListener('click', (e) => {
239
+ if (e.target.checked) {
240
+ if (e.target.value === 'is_printdisabled') lendingStatus1.is_printdisabled = true;
241
+ if (e.target.value === 'user_is_printdisabled') lendingStatus1.user_is_printdisabled = true;
242
+ } else {
243
+ if (e.target.value === 'is_printdisabled') lendingStatus1.is_printdisabled = false;
244
+ if (e.target.value === 'user_is_printdisabled') lendingStatus1.user_is_printdisabled = false;
245
+ }
246
+
247
+ let lending = {...defaultLendingStatus, ...lendingStatus1};
248
+ iaBookActions.lendingStatus = lending;
249
+ });
250
+ });
251
+
252
+ document.querySelectorAll('.availableToBrowse input[type=radio]').forEach((item) => {
253
+ item.addEventListener('click', async (e) => {
254
+ if (e.target.value === 'user_has_browsed') {
255
+ lendingStatus1.user_has_browsed = true;
256
+ lendingStatus1.available_to_browse = false;
257
+ lendingStatus1.secondsLeftOnLoan = secondsLeftOnLoan;
258
+ lendingStatus1.browsingExpired = false;
259
+
260
+ const expireDate = new Date(
261
+ new Date().getTime() + iaBookActions.loanRenewTimeConfig.loanTotalTime * 1000
262
+ );
263
+ try {
264
+ // set a value
265
+ await localCache.set({
266
+ key: `${identifier}-loanTime`,
267
+ value: expireDate,
268
+ ttl: Number(iaBookActions.loanRenewTimeConfig.loanTotalTime),
269
+ });
270
+
271
+ await localCache.delete(`${identifier}-pageChangedTime`);
272
+ } catch (error) {}
273
+
274
+ } else if(e.target.value === 'browsingExpired') {
275
+ lendingStatus1.user_has_browsed = true;
276
+ lendingStatus1.available_to_browse = false;
277
+ lendingStatus1.secondsLeftOnLoan = 0;
278
+ lendingStatus1.browsingExpired = true;
279
+ } else if (e.target.value === 'available_to_browse') {
280
+ lendingStatus1.available_to_browse = true;
281
+ lendingStatus1.user_has_browsed = false;
282
+ }
283
+
284
+ // wait for a while
285
+ setTimeout(() => {
286
+ let lending = {...defaultLendingStatus, ...lendingStatus1};
287
+ iaBookActions.lendingStatus = lending;
288
+ }, 10);
289
+ });
290
+ });
291
+
292
+ document.querySelector('#show_warning_modal').addEventListener('click', async () => {
293
+ if (!iaBookActions.lendingStatus.user_has_browsed) {
294
+ const newLendingStatus = { ...iaBookActions.lendingStatus, user_has_browsed: true };
295
+ iaBookActions.lendingStatus = newLendingStatus;
296
+ await iaBookActions.updateComplete;
297
+ }
298
+
299
+ iaBookActions.showWarningModal();
300
+
301
+ if (iaBookActions.lendingStatus.user_has_browsed) {
302
+ iaBookActions.showWarningModal();
303
+ }
304
+ });
305
+
306
+ document.querySelector('#show_expired_modal').addEventListener('click', async () => {
307
+ if (!iaBookActions.lendingStatus.user_has_browsed) {
308
+ const newLendingStatus = { ...iaBookActions.lendingStatus, user_has_browsed: true };
309
+ iaBookActions.lendingStatus = newLendingStatus;
310
+ await iaBookActions.updateComplete;
311
+ }
312
+ await new Promise(resolve => setTimeout(resolve, 5000));
313
+
314
+ iaBookActions.browseHasExpired();
315
+ });
316
+
317
+ document.querySelector('#resync_timer').addEventListener('click', async (e) => {
318
+ if (iaBookActions.lendingStatus.user_has_browsed) {
319
+ document.querySelector('ia-book-actions').dispatchEvent(
320
+ new Event('visibilitychange', {
321
+ detail: {},
322
+ bubbles: true,
323
+ composed: true,
324
+ })
325
+ );
326
+ }
327
+ await new Promise(resolve => setTimeout(resolve, 5000));
328
+ });
329
+
330
+ document.querySelectorAll('.availableToBorrow input[type=radio]').forEach((item) => {
331
+ item.addEventListener('click', (e) => {
332
+ if (e.target.value === 'available_to_borrow') {
333
+ lendingStatus1.available_to_borrow = true;
334
+ lendingStatus1.user_on_waitlist = false;
335
+ lendingStatus1.available_to_waitlist = false;
336
+ lendingStatus1.user_has_borrowed = false;
337
+ } else if (e.target.value === 'user_can_claim_waitlist') {
338
+ lendingStatus1.available_to_borrow = true;
339
+ lendingStatus1.user_on_waitlist = true;
340
+ lendingStatus1.user_can_claim_waitlist = true;
341
+ lendingStatus1.user_has_borrowed = false;
342
+ lendingStatus1.available_to_waitlist = false;
343
+ } else if (e.target.value === 'user_on_waitlist') {
344
+ lendingStatus1.available_to_borrow = false;
345
+ lendingStatus1.user_on_waitlist = true;
346
+ lendingStatus1.available_to_waitlist = false;
347
+ lendingStatus1.user_has_borrowed = false;
348
+ } else if (e.target.value === 'available_to_waitlist') {
349
+ lendingStatus1.available_to_borrow = false;
350
+ lendingStatus1.user_on_waitlist = false;
351
+ lendingStatus1.available_to_waitlist = true;
352
+ lendingStatus1.user_has_borrowed = false;
353
+ } else if (e.target.value === 'user_has_borrowed') {
354
+ lendingStatus1.available_to_borrow = false;
355
+ lendingStatus1.user_on_waitlist = false;
356
+ lendingStatus1.available_to_waitlist = false;
357
+ lendingStatus1.user_has_borrowed = true;
358
+ }
359
+
360
+ let lending = {...defaultLendingStatus, ...lendingStatus1};
361
+ iaBookActions.lendingStatus = lending;
362
+ });
363
+ });
364
+
365
+ document.querySelectorAll('.purchase input[type=checkbox]').forEach((item) => {
366
+ item.addEventListener('click', (e) => {
367
+ if (e.target.checked) {
368
+ bwbPurchaseUrl = 'https://www.google.com';
369
+ } else {
370
+ bwbPurchaseUrl = ''
371
+ }
372
+ iaBookActions.bwbPurchaseUrl = bwbPurchaseUrl;
373
+ })
374
+ });
375
+
376
+ document.querySelector('.pageChangedEvent').addEventListener('click', () => {
377
+ // dispatch event to say book page has flipped
378
+ document.querySelector('ia-book-actions').dispatchEvent(
379
+ new CustomEvent('BookReader:userAction', {
380
+ detail: {},
381
+ bubbles: true,
382
+ composed: true,
383
+ })
384
+ );
385
+ })
386
+
387
+ // listen for IABookReader:BrowsingHasExpired
388
+ window.addEventListener('IABookReader:BrowsingHasExpired', () => {
389
+ console.log('IABookReader:BrowsingHasExpired EVENT FIRED');
390
+ });
391
+ </script>
392
+
393
+ <div>
394
+ <fieldset>
395
+ <legend>Dev testing for book title bar</legend>
396
+ <table class="initial">
397
+ <tr>
398
+ <td class="titleBar">
399
+ <label><input type="checkbox" name="title" value="title" />Title Bar</label>
400
+ </td>
401
+ <td class="searchParam">
402
+ <label><input type="checkbox" name="search" value="Search Param" />Search Param</label>
403
+ </td>
404
+ <td>
405
+ <label class="ia-button"><button class="pageChangedEvent">Page Change Event</button></label>
406
+ </td>
407
+ </tr>
408
+ </table>
409
+ </fieldset>
410
+
411
+ <fieldset>
412
+ <legend>Dev testing for book action bar</legend>
413
+ <table class="initial">
414
+ <tr colspan='5'>
415
+ <td class="errorEnable"><label><input type="checkbox" name="errorEnable" value="errorEnable" />Do you want to see error response?</label></td>
416
+ </tr>
417
+ <tr>
418
+ <td class="userState">
419
+ <label><input type="checkbox" name="user" value="isAdmin" />isAdmin</label>
420
+ <label><input type="checkbox" name="user" value="isLoggedIn" />isLoggedIn</label>
421
+ </td>
422
+ <td class="printDisabled">
423
+ <label><input type="checkbox" name="is_printdisabled" value="is_printdisabled" />is_printdisabled</label>
424
+ <label><input type="checkbox" name="user_is_printdisabled" value="user_is_printdisabled" />user_is_printdisabled</label>
425
+ </td>
426
+ <td class="availableToBrowse">
427
+ <label><input type="radio" name="browse" value="available_to_browse" />available_to_browse</label>
428
+ <label><input type="radio" name="browse" value="user_has_browsed" />user_has_browsed</label>
429
+ <label><input type="radio" name="browse" value="browsingExpired" />browsingExpired</label>
430
+ </td>
431
+ <td class="availableToBorrow">
432
+ <label><input type="radio" name="borrow" value="available_to_borrow" />available_to_borrow</label>
433
+ <label><input type="radio" name="borrow" value="user_on_waitlist" />user_on_waitlist</label>
434
+ <label><input type="radio" name="borrow" value="user_can_claim_waitlist" />user_can_claim_waitlist</label>
435
+ <label><input type="radio" name="borrow" value="available_to_waitlist" />available_to_waitlist</label>
436
+ <label><input type="radio" name="borrow" value="user_has_borrowed" />user_has_borrowed</label>
437
+ </td>
438
+ <td class="purchase">
439
+ <label><input type="checkbox" name="purchase" value="purchase" />purchase</label>
440
+ </td>
441
+
442
+ <td class="reflect">
443
+ <input type="button" style="padding: 10px;
444
+ background: #194880;
445
+ color: white;
446
+ font-weight: bold; display: none" name="purchase" value="Reflect" />
447
+ </td>
448
+ </tr>
449
+ <tr>
450
+ <td>
451
+ <fieldset style="margin-top: 10px;">
452
+ <legend style="color: black;">controls for browsed book</legend>
453
+ <table aria-hidden="true">
454
+ <tbody><tr>
455
+ <td class="action-button">
456
+ <button id="show_warning_modal">When Browsing (`user_has_browsed`),<br> show Warning Modal</button>
457
+ </td>
458
+ <td class="action-button">
459
+ <button id="show_expired_modal">While Browsing (`user_has_browsed`),<br> Expire book & show its modal</button>
460
+ </td>
461
+ <td class="action-button">
462
+ <button id="resync_timer">While Browsing (`user_has_browsed`),<br> resync timer</button>
463
+ </td>
464
+ </tr>
465
+ </tbody></table>
466
+ </fieldset>
467
+ </td>
468
+ </tr>
469
+ </table>
470
+ </fieldset>
471
+ </div>
472
+ <modal-manager></modal-manager>
473
+ </body>
474
+ </html>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@internetarchive/ia-book-actions",
3
- "version": "1.1.0-alpha8",
3
+ "version": "1.1.0",
4
4
  "description": "Bookreader actions for borrowable book",
5
5
  "author": "Internet Archive",
6
6
  "license": "AGPL-3.0-only",
@@ -11,11 +11,16 @@
11
11
  },
12
12
  "scripts": {
13
13
  "start": "web-dev-server",
14
- "lint": "eslint --ext .js,.html . --ignore-path .gitignore && prettier \"**/*.js\" --check --ignore-path .gitignore",
15
- "format": "eslint --ext .js,.html . --fix --ignore-path .gitignore && prettier \"**/*.js\" --write --ignore-path .gitignore",
14
+ "prepare:ghpages": "rimraf ghpages && yarn run prepare && vite build",
15
+ "prepare": "rimraf dist && husky install",
16
+ "lint": "eslint --ext \"./src/*.js\",.html . --ignore-path .gitignore && prettier \"./src/*.js\" --check --ignore-path .gitignore",
17
+ "format": "eslint --ext \"./src/*.js\",.html . --fix --ignore-path .gitignore && prettier \"./src/*.js\" --write --ignore-path .gitignore",
16
18
  "test": "yarn run lint && yarn test:fast",
17
- "test:fast": "web-test-runner --coverage",
18
- "test:watch": "web-test-runner --watch"
19
+ "test:fast": "web-test-runner --coverage --playwright --browsers chromium firefox webkit",
20
+ "test:watch": "web-test-runner --watch --playwright --browsers chromium firefox webkit",
21
+ "deploy": "yarn run deploy:run -e $(git branch --show-current)",
22
+ "deploy:run": "yarn run prepare:ghpages && touch ghpages/.nojekyll && yarn run deploy:gh",
23
+ "deploy:gh": "gh-pages -t -d ghpages -m \"Build for $(git log --pretty=format:\"%h %an %ai %s\" -n1) [skip ci]\""
19
24
  },
20
25
  "dependencies": {
21
26
  "@internetarchive/icon-info": "^1.2.6",
@@ -38,10 +43,13 @@
38
43
  "eslint-plugin-lit": "^1.3.0",
39
44
  "eslint-plugin-lit-a11y": "^1.0.1",
40
45
  "eslint-plugin-wc": "^1.3.0",
41
- "husky": "^1.0.0",
46
+ "gh-pages": "^4.0.0",
47
+ "husky": "^7.0.0",
42
48
  "lint-staged": "^10.0.0",
43
49
  "prettier": "^2.0.4",
44
- "sinon": "^13.0.2"
50
+ "sinon": "^13.0.2",
51
+ "vite": "^2.9.9",
52
+ "rimraf": "^3.0.2"
45
53
  },
46
54
  "eslintConfig": {
47
55
  "extends": [
@@ -118,7 +118,7 @@ export const canBrowseAndBorrow = {
118
118
  loanStartDate: '2021-07-30 09:57:40',
119
119
  loansUrl: '/details/@neeraj-archive?tab=loans#loans-on-loan',
120
120
  maxLoans: 10,
121
- secondsLeftOnLoan: 170,
121
+ secondsLeftOnLoan: 0,
122
122
  shouldProtectImages: true,
123
123
  totalWaitlistCount: 0,
124
124
  userHasBorrowed: false,
@@ -47,7 +47,7 @@ export default class TimerCountdown extends LitElement {
47
47
  }
48
48
 
49
49
  render() {
50
- const viewClass = this.displayTime ? 'view' : 'hide1';
50
+ const viewClass = this.displayTime ? 'view' : 'hide';
51
51
  return html`
52
52
  <button
53
53
  id="timer-counter"
@@ -57,7 +57,8 @@ export default class TimerCountdown extends LitElement {
57
57
  }}
58
58
  role="timer"
59
59
  >
60
- <span>${this.minutesLeftOnLoan}</span>
60
+ <span>${this.minutesLeftOnLoan} - </span>
61
+ <span class="second">${Number(this.secondsLeftOnLoan)}</span>
61
62
  <span class="sr-only">${this.remainingTime} left</span>
62
63
  </button>
63
64
  `;
@@ -32,7 +32,12 @@ export default async function ActionsHandlerService(options) {
32
32
  location?.href?.indexOf('?error=true') !== -1 &&
33
33
  location?.hostname !== 'archive.org';
34
34
 
35
- if (location?.pathname === '/demo/') baseHost = `/demo/`;
35
+ const testHostname = ['localhost', 'internetarchive.github.io'];
36
+ let isTest = false;
37
+ if (testHostname.includes(location.hostname)) {
38
+ isTest = true;
39
+ baseHost = location.href;
40
+ }
36
41
 
37
42
  let formData = new FormData();
38
43
  formData.append('action', option.action);
@@ -51,8 +56,8 @@ export default async function ActionsHandlerService(options) {
51
56
  };
52
57
  }
53
58
 
54
- // return success response for /demo/ server...
55
- if (baseHost == '/demo/1' || baseHost == '/demo/') {
59
+ // return success response for localhost server...
60
+ if (isTest) {
56
61
  if (
57
62
  option?.action == 'renew_loan' ||
58
63
  option?.action == 'return_loan'
@@ -8,7 +8,8 @@ const log =
8
8
  location.host.match(/^(www|cat)-[a-z0-9]+\.archive\.org$/) ||
9
9
  location.host.match(/\.code\.archive\.org$/) ||
10
10
  location.host.match(/\.dev\.archive\.org$/) ||
11
- location.host.match(/^ia-petabox-/)
11
+ location.host.match(/^ia-petabox-/) ||
12
+ location.host.match(/^internetarchive/)
12
13
  ? // eslint-disable-next-line no-console
13
14
  console.log.bind(console) // convenient, no? Stateless function
14
15
  : () => {};
@@ -4,7 +4,6 @@ import { html, css, LitElement, nothing } from 'lit';
4
4
 
5
5
  import { SharedResizeObserver } from '@internetarchive/shared-resize-observer';
6
6
  import { ModalConfig } from '@internetarchive/modal-manager';
7
- // import { ToastConfig } from '@internetarchive/toast-manager';
8
7
  import { LocalCache } from '@internetarchive/local-cache';
9
8
 
10
9
  import './components/collapsible-action-group.js';
@@ -60,6 +59,7 @@ export default class IABookActions extends LitElement {
60
59
  disableActionGroup: { type: Boolean },
61
60
  modal: { Object },
62
61
  tokenDelay: { type: Number },
62
+ timerExecutionSeconds: { type: Number },
63
63
  localCache: { type: Object },
64
64
  loanRenewTimeConfig: { type: Object },
65
65
  loanRenewResult: { type: Object },
@@ -80,6 +80,7 @@ export default class IABookActions extends LitElement {
80
80
  this.sharedObserver = undefined;
81
81
  this.disableActionGroup = false;
82
82
  this.tokenDelay = 120; // in seconds
83
+ this.timerExecutionSeconds = 30;
83
84
 
84
85
  // private props
85
86
  this.postInitComplete = false;
@@ -90,10 +91,7 @@ export default class IABookActions extends LitElement {
90
91
  this.lendingOptions = {};
91
92
  this.borrowType = null; // 'browsed'|'borrowed'
92
93
  this.browseTimer = undefined; // timeout
93
- this.timerExecutionSeconds = 5;
94
-
95
- /** @deprecated */
96
- this.toast = undefined;
94
+ this.timeWhenTimerStart = undefined;
97
95
 
98
96
  /**
99
97
  * when user click on [return the book] button on warning modal
@@ -118,7 +116,7 @@ export default class IABookActions extends LitElement {
118
116
  * contains one hour auto-loan-renew response
119
117
  *
120
118
  * @type {object} loanRenewResult
121
- * @property {string} texts - texts messages need to show in toast message
119
+ * @property {string} texts - texts messages shows in modal
122
120
  * @property {boolean} renewNow - key to determine if need to renew now
123
121
  * @property {number} secondsLeft - seconds left in active loan
124
122
  */
@@ -129,10 +127,19 @@ export default class IABookActions extends LitElement {
129
127
  // clear all intervals for lending system
130
128
  window?.IALendingIntervals?.clearAll();
131
129
 
132
- window?.Sentry?.captureMessage(sentryLogs.disconnectedCallback);
130
+ this.sentryCaptureMsg(sentryLogs.disconnectedCallback);
133
131
  this.disconnectResizeObserver();
134
132
  }
135
133
 
134
+ /**
135
+ * send log messages to sentry
136
+ * @param {string} msg
137
+ */
138
+ sentryCaptureMsg(msg) {
139
+ log(window?.Sentry);
140
+ window?.Sentry?.captureMessage(msg);
141
+ }
142
+
136
143
  firstUpdated() {
137
144
  // bind auto loan renew events
138
145
  this.bindLoanRenewEvents();
@@ -224,7 +231,7 @@ export default class IABookActions extends LitElement {
224
231
  log('setupLendingToolbarActions > hasExpired --- ');
225
232
 
226
233
  if (!this.tokenPoller) {
227
- window?.Sentry?.captureMessage(sentryLogs.bookWasExpired);
234
+ this.sentryCaptureMsg(sentryLogs.bookWasExpired);
228
235
  }
229
236
  window?.IALendingIntervals?.clearAll();
230
237
 
@@ -286,12 +293,36 @@ export default class IABookActions extends LitElement {
286
293
 
287
294
  /**
288
295
  * auto-renew interval may stale when tab is in background,
289
- * so [visibilitychange] event can trigger to check timer to show relavent messages
296
+ * so when user open that tab again,
297
+ * the [visibilitychange] event can trigger timer to show relavent messages
290
298
  */
291
- document.addEventListener("visibilitychange", () => {
292
- log('visibilitychange');
299
+ document.addEventListener('visibilitychange', async () => {
293
300
  if (!document.hidden) {
294
- this.startTimerCountdown();
301
+ log(
302
+ 'visibilitychange event execute:------------------ ',
303
+ new Date().getMinutes(),
304
+ new Date().getSeconds(),
305
+ this.borrowType
306
+ );
307
+
308
+ if (
309
+ this.borrowType === 'browsed' &&
310
+ this.lendingStatus.browsingExpired === false
311
+ ) {
312
+ const loanTime = await this.localCache.get(
313
+ `${this.identifier}-loanTime`
314
+ );
315
+
316
+ // number of seconds left in current loan
317
+ const secondsLeft = Math.round((loanTime - new Date()) / 1000);
318
+
319
+ if (secondsLeft >= this.timerExecutionSeconds) {
320
+ this.loanStatusCheckInterval(Number(secondsLeft));
321
+ } else {
322
+ this.browseHasExpired();
323
+ this.disconnectedCallback();
324
+ }
325
+ }
295
326
  }
296
327
  });
297
328
  }
@@ -473,61 +504,6 @@ export default class IABookActions extends LitElement {
473
504
  await this.modal?.showModal({ config, customModalContent });
474
505
  }
475
506
 
476
- /**
477
- * @deprecated
478
- * create / select the toast-template component on DOM
479
- *
480
- * @memberof IABookActions
481
- */
482
- async useToastManager() {
483
- if (!this.toast) {
484
- this.toast = this.shadowRoot.querySelector('toast-template');
485
-
486
- if (!this.toast) this.toast = document.createElement('toast-template');
487
- }
488
-
489
- await this.shadowRoot.appendChild(this.toast);
490
- }
491
-
492
- /**
493
- * @deprecated
494
- * close/hide toast message
495
- */
496
- async closeToastManager() {
497
- this.toast = this.shadowRoot.querySelector('toast-template');
498
- this.toast?.remove();
499
- }
500
-
501
- /**
502
- * @deprecated
503
- * Show toast messages on some specific loan renew features. e.g.
504
- * - show message when book is auto renewed
505
- * - show message when book is expired
506
- * - show message having remaining time when book is about to auto returned
507
- */
508
- // async showToastMessage() {
509
- // await this.useToastManager();
510
-
511
- // // if secondsLeft < 60, consider it 1 minute
512
- // let { secondsLeft } = this.loanRenewResult;
513
- // if (secondsLeft === undefined) {
514
- // secondsLeft = this.lendingStatus.secondsLeftOnLoan;
515
- // } else {
516
- // secondsLeft = secondsLeft > 60 ? secondsLeft : 60;
517
- // }
518
-
519
- // const config = new ToastConfig();
520
- // config.dismisOnClick = true;
521
- // config.texts = this.loanRenewHelper?.getMessageTexts(
522
- // this.loanRenewResult.texts,
523
- // secondsLeft
524
- // );
525
-
526
- // this.toast?.showToast({
527
- // config,
528
- // });
529
- // }
530
-
531
507
  /**
532
508
  * Execute when loan is expired
533
509
  */
@@ -553,7 +529,7 @@ export default class IABookActions extends LitElement {
553
529
 
554
530
  await this.showExpiredModal();
555
531
 
556
- window?.Sentry?.captureMessage(sentryLogs.browseHasExpired);
532
+ this.sentryCaptureMsg(sentryLogs.browseHasExpired);
557
533
  }
558
534
 
559
535
  async startBrowseTimer() {
@@ -656,7 +632,10 @@ export default class IABookActions extends LitElement {
656
632
  if (this.loanRenewResult.renewNow) {
657
633
  // Now, let's reset loan duration & this.lendingStatus
658
634
  const loanTime = await this.localCache.get(`${this.identifier}-loanTime`);
659
- const secondsLeft = Math.round((loanTime - new Date()) / 1000); // different in seconds
635
+
636
+ // number of seconds left in current loan
637
+ const secondsLeft = Math.round((loanTime - new Date()) / 1000);
638
+ log(loanTime, secondsLeft);
660
639
 
661
640
  log('IABookActions: handleLoanAutoRenewed --- ', {
662
641
  ajaxResponse: detail?.data,
@@ -676,7 +655,7 @@ export default class IABookActions extends LitElement {
676
655
  this.modal?.closeModal();
677
656
  this.modal.removeAttribute('id');
678
657
  this.modal.customModalContent = nothing;
679
- window?.Sentry?.captureMessage(sentryLogs.bookHasRenewed);
658
+ this.sentryCaptureMsg(sentryLogs.bookHasRenewed);
680
659
  }
681
660
  }
682
661
 
@@ -685,59 +664,80 @@ export default class IABookActions extends LitElement {
685
664
  *
686
665
  * @memberof IABookActions
687
666
  */
688
- startTimerCountdown() {
667
+ async startTimerCountdown() {
689
668
  window?.IALendingIntervals?.clearTimerCountdown();
690
669
 
691
- let secondsLeft = Number(this.lendingStatus.secondsLeftOnLoan);
670
+ const secondsLeft = Number(this.lendingStatus.secondsLeftOnLoan);
692
671
  this.timeWhenTimerStart = new Date();
693
672
 
694
673
  window.IALendingIntervals.timerCountdown = setInterval(async () => {
695
- secondsLeft -= this.timerExecutionSeconds;
696
- secondsLeft = Math.round(secondsLeft); // round number
697
-
698
- // re-sync timer if gone off because of background window
699
- // side effect: updates this.lendingStatus & kicks off lifecycle,
700
- // if really updated, escape from here
701
- const resyncd = this.reSyncTimerIfGoneOff(secondsLeft);
702
-
703
- if (resyncd.hasSynced) {
704
- secondsLeft = resyncd.whatShouldLeft;
674
+ // interval execution block
675
+ await this.loanStatusCheckInterval(secondsLeft);
676
+ }, this.timerExecutionSeconds * 1000);
677
+ }
705
678
 
706
- log('startTimerCountdown --- stale, timer has resyncd', {
707
- secondsLeft,
708
- });
709
- }
679
+ /**
680
+ * setInterval execution function do following things
681
+ * - if setInterval timer gone off, let's resync
682
+ * - loan-renew attempt to renew the loan OR show relavant message
683
+ * - if loan has expired, clear interval timers
684
+ *
685
+ * @param {Number} secondsLeftOnLoan
686
+ */
687
+ async loanStatusCheckInterval(secondsLeftOnLoan) {
688
+ let secondsLeft = secondsLeftOnLoan;
689
+ secondsLeft -= this.timerExecutionSeconds;
690
+ secondsLeft = Math.round(secondsLeft); // round number
691
+
692
+ // re-sync timer if gone off because of background window
693
+ // side effect: updates this.lendingStatus & kicks off lifecycle,
694
+ // if really updated, escape from here
695
+ const resyncd = await this.reSyncTimerIfGoneOff(secondsLeft);
696
+
697
+ if (resyncd.hasSynced) {
698
+ secondsLeft = resyncd.whatShouldLeft;
699
+ log('startTimerCountdown --- stale, timer has resyncd', {
700
+ secondsLeft,
701
+ });
702
+ }
710
703
 
711
- log('startTimerCountdown --- countdown still valid. continue...', {
704
+ log(
705
+ 'startTimerCountdown --- countdown still valid. continue...',
706
+ {
712
707
  resyncd,
713
708
  secondsLeft,
714
709
  loanRenewAtLast: this.loanRenewTimeConfig.loanRenewAtLast,
715
- }, new Date().getMinutes(), new Date().getSeconds());
710
+ },
711
+ 'time: ',
712
+ new Date().getMinutes(),
713
+ ':',
714
+ new Date().getSeconds(),
715
+ ', timerDelay: ',
716
+ this.timerExecutionSeconds
717
+ );
716
718
 
717
- /**
718
- * execute from last 10th minute to 0th minute
719
- * - 10th - to check if user has viewed
720
- * - till 0th - to show warning msg with remaining time to auto expired
721
- * @see IABookActions::bindLoanRenewEvents
722
- */
723
- if (secondsLeft <= this.loanRenewTimeConfig.loanRenewAtLast) {
724
- await this.loanRenewAttempt(secondsLeft);
725
- }
719
+ /**
720
+ * execute from last 10th minutes to 0th minute
721
+ * - 10th - to check if user has viewed
722
+ * - till 0th - to show warning msg with remaining time to auto expired
723
+ * @see IABookActions::bindLoanRenewEvents
724
+ */
725
+ if (secondsLeft <= this.loanRenewTimeConfig.loanRenewAtLast) {
726
+ await this.loanRenewAttempt(secondsLeft);
727
+ }
726
728
 
727
- // clear interval in secondsLeft if less
728
- if (secondsLeft <= this.timerExecutionSeconds) {
729
- this.disconnectedCallback();
730
- window?.Sentry?.captureMessage(sentryLogs.clearOneHourTimer);
731
- }
732
- }, this.timerExecutionSeconds * 1000);
729
+ // clear interval in secondsLeft if less
730
+ if (secondsLeft <= this.timerExecutionSeconds) {
731
+ this.disconnectedCallback();
732
+ this.sentryCaptureMsg(sentryLogs.clearOneHourTimer);
733
+ }
733
734
  }
734
735
 
735
736
  /**
736
737
  * helper function to determine if timer is not in sync properly
737
738
  *
738
739
  * @param {number} timerSecondsLeft - actual seconds left get from setInterval
739
- *
740
- * @memberof TimerCountdown
740
+ * @returns {Object} { hasSynced: [boolean], whatShouldLeft: [number] }
741
741
  */
742
742
  async reSyncTimerIfGoneOff(timerSecondsLeft) {
743
743
  const currentTime = new Date();
@@ -749,6 +749,15 @@ export default class IABookActions extends LitElement {
749
749
  const secondsShouldLeft =
750
750
  this.lendingStatus.secondsLeftOnLoan - diffInSeconds;
751
751
 
752
+ log('currentTime: ', currentTime);
753
+ log('timeWhenTimerStart: ', this.timeWhenTimerStart);
754
+ log('diffInSeconds: ', diffInSeconds);
755
+ log('secondsShouldLeft: ', secondsShouldLeft);
756
+ log(
757
+ 'this.lendingStatus.secondsLeftOnLoan: ',
758
+ this.lendingStatus.secondsLeftOnLoan
759
+ );
760
+
752
761
  // convert in minutes
753
762
  const whatIsleft = Math.round(timerSecondsLeft);
754
763
  const whatShouldLeft = Math.round(secondsShouldLeft);
@@ -772,9 +781,9 @@ export default class IABookActions extends LitElement {
772
781
 
773
782
  if (whatIsleft !== whatShouldLeft) {
774
783
  log(`reSyncTimerIfGoneOff --- let's re-sync.`);
775
-
776
784
  return { hasSynced: true, whatShouldLeft };
777
785
  }
786
+
778
787
  return { hasSynced: false, whatShouldLeft };
779
788
  }
780
789
 
@@ -994,12 +1003,6 @@ export default class IABookActions extends LitElement {
994
1003
  justify-content: center;
995
1004
  flex-wrap: wrap;
996
1005
  }
997
-
998
- toast-template {
999
- --toastTopMargin: 80px;
1000
- --toastBGColor: #333;
1001
- --toastFontColor: #fff;
1002
- }
1003
1006
  `;
1004
1007
  }
1005
1008
  }
@@ -13,7 +13,9 @@ const container = ({
13
13
  .loanRenewAtLast=${loanRenewAtLast}
14
14
  ></timer-countdown>`;
15
15
 
16
- describe('<timer-countdown>', () => {
16
+ describe('<timer-countdown>', async () => {
17
+ await aTimeout(5000);
18
+
17
19
  it('timer interval is undefined when loan is expired', async () => {
18
20
  const el = await fixture(
19
21
  container({
@@ -36,7 +38,6 @@ describe('<timer-countdown>', () => {
36
38
  })
37
39
  );
38
40
 
39
- await aTimeout(1900);
40
41
  await el.updateComplete;
41
42
 
42
43
  expect(el.secondsLeftOnLoan).to.equal(2);
@@ -376,7 +376,8 @@ describe('Browsing expired status', () => {
376
376
  });
377
377
  });
378
378
 
379
- describe('Auto renew one hour loan', () => {
379
+ describe('Auto renew one hour loan', async () => {
380
+ await aTimeout(5000);
380
381
  it('Book is browsing and renewed it now', async () => {
381
382
  const loanTime = 88;
382
383
  const el = await fixture(
@@ -449,6 +450,61 @@ describe('Auto renew one hour loan', () => {
449
450
  });
450
451
  });
451
452
 
453
+ describe('Visibility change API for document', async () => {
454
+ await aTimeout(5000);
455
+
456
+ beforeEach(() => {
457
+ Object.defineProperty(document, 'hidden', {
458
+ value: false,
459
+ configurable: true,
460
+ });
461
+ });
462
+
463
+ afterEach(() => {
464
+ Object.defineProperty(document, 'hidden', {
465
+ value: true,
466
+ configurable: true,
467
+ });
468
+ });
469
+
470
+ it('when book is not expired', async () => {
471
+ const el = await fixture(
472
+ container({
473
+ userid: '@user1',
474
+ identifier: 'foobar',
475
+ lendingStatus: {
476
+ user_has_browsed: true,
477
+ browsingExpired: false,
478
+ },
479
+ })
480
+ );
481
+ await el.updateComplete;
482
+
483
+ const spy = Sinon.spy(el, 'loanStatusCheckInterval');
484
+ document.dispatchEvent(new Event('visibilitychange'));
485
+
486
+ expect(spy.calledOnce).to.be.true;
487
+ });
488
+
489
+ it('when book is already expired', async () => {
490
+ const el = await fixture(
491
+ container({
492
+ userid: '@user1',
493
+ identifier: 'foobar',
494
+ lendingStatus: {
495
+ user_has_browsed: true,
496
+ },
497
+ })
498
+ );
499
+ await el.updateComplete;
500
+
501
+ const spy = Sinon.spy(el, 'browseHasExpired');
502
+ document.dispatchEvent(new Event('visibilitychange'));
503
+
504
+ expect(spy.calledOnce).to.be.true;
505
+ });
506
+ });
507
+
452
508
  describe('Shared Resize Observer', () => {
453
509
  it('can receive a Shared Resize Observer', async () => {
454
510
  const sharedObserverStub = new SharedResizeObserver();
package/vite.config.ts ADDED
@@ -0,0 +1,22 @@
1
+ import { defineConfig } from 'vite';
2
+ import { resolve } from 'path';
3
+
4
+ // https://vitejs.dev/config/
5
+ export default defineConfig({
6
+ base: '',
7
+ build: {
8
+ outDir: 'ghpages',
9
+ manifest: true,
10
+ rollupOptions: {
11
+ input: {
12
+ main: resolve(__dirname, 'index.html'),
13
+ },
14
+ },
15
+ },
16
+ server: {
17
+ host: true,
18
+ port: 8080,
19
+ open: true,
20
+ cors: true,
21
+ },
22
+ });
@@ -5,7 +5,7 @@ const hmr = process.argv.includes('--hmr');
5
5
 
6
6
  export default /** @type {import('@web/dev-server').DevServerConfig} */ ({
7
7
  nodeResolve: true,
8
- open: '/demo/',
8
+ open: '/',
9
9
  watch: !hmr,
10
10
 
11
11
  /** Compile JS for older browsers. Requires @web/dev-server-esbuild plugin */