@internetarchive/ia-book-actions 1.1.0-alpha14 → 1.1.0-alpha15

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.
package/demo/index.html CHANGED
@@ -34,6 +34,8 @@
34
34
  --primaryCTAFillRGB: 25, 72, 128;
35
35
  --primaryErrorCTAFillRGB: 229, 28, 38;
36
36
  --primaryDisableCTAFill: #767676;
37
+ background: var(--secondaryBGColor);
38
+ color: var(--primaryTextColor);
37
39
  }
38
40
 
39
41
  fieldset {
@@ -76,6 +78,13 @@
76
78
  display: inline-grid;
77
79
  }
78
80
 
81
+ .action-button button {
82
+ padding: 6px;
83
+ cursor: pointer;
84
+ border: 2px solid #194880;
85
+ border-radius: 10px;
86
+ }
87
+
79
88
  body.modal-manager-open {
80
89
  overflow: hidden;
81
90
  }
@@ -128,10 +137,11 @@
128
137
  iaBookActions.modalConfig = modalConfig;
129
138
  iaBookActions.lendingBarPostInit = lendingBarPostInit;
130
139
  iaBookActions.tokenDelay = 2; // 2 seconds
140
+ iaBookActions.timerExecutionSeconds = 4 // 4 seconds
131
141
  iaBookActions.returnUrl = '';
132
142
  iaBookActions.localCache = localCache;
133
143
 
134
- let loanTotalTime2Min = { loanTotalTime: 65, loanRenewAtLast: 60, pageChangedInLast: 15 };
144
+ let loanTotalTime2Min = { loanTotalTime: 120, loanRenewAtLast: 80, pageChangedInLast: 15 };
135
145
  let loanTotalTime5Min = { loanTotalTime: 300, loanRenewAtLast: 180, pageChangedInLast: 15 };
136
146
  let loanTotalTime1Hour = { loanTotalTime: 3600, loanRenewAtLast: 660, pageChangedInLast: 900 };
137
147
 
@@ -286,6 +296,19 @@
286
296
  iaBookActions.browseHasExpired();
287
297
  });
288
298
 
299
+ document.querySelector('#resync_timer').addEventListener('click', async (e) => {
300
+ if (iaBookActions.lendingStatus.user_has_browsed) {
301
+ document.querySelector('ia-book-actions').dispatchEvent(
302
+ new Event('visibilitychange', {
303
+ detail: {},
304
+ bubbles: true,
305
+ composed: true,
306
+ })
307
+ );
308
+ }
309
+ await new Promise(resolve => setTimeout(resolve, 5000));
310
+ });
311
+
289
312
  document.querySelectorAll('.availableToBorrow input[type=radio]').forEach((item) => {
290
313
  item.addEventListener('click', (e) => {
291
314
  if (e.target.value === 'available_to_borrow') {
@@ -359,9 +382,6 @@
359
382
  </td>
360
383
  <td class="searchParam">
361
384
  <label><input type="checkbox" name="search" value="Search Param" />Search Param</label>
362
- <td>
363
- <button class="pageChangedEvent">Page Change Event</button>
364
- </td>
365
385
  </tr>
366
386
  </table>
367
387
  </fieldset>
@@ -385,12 +405,6 @@
385
405
  <label><input type="radio" name="browse" value="available_to_browse" />available_to_browse</label>
386
406
  <label><input type="radio" name="browse" value="user_has_browsed" />user_has_browsed</label>
387
407
  <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
408
  </td>
395
409
  <td class="availableToBorrow">
396
410
  <label><input type="radio" name="borrow" value="available_to_borrow" />available_to_borrow</label>
@@ -410,6 +424,29 @@
410
424
  font-weight: bold; display: none" name="purchase" value="Reflect" />
411
425
  </td>
412
426
  </tr>
427
+ <tr>
428
+ <td>
429
+ <fieldset style="margin-top: 10px;">
430
+ <legend style="color: black;">controls for browsed book</legend>
431
+ <table aria-hidden="true">
432
+ <tbody><tr>
433
+ <td class="action-button">
434
+ <button class="pageChangedEvent">Page Change Event</button>
435
+ </td>
436
+ <td class="action-button">
437
+ <button id="show_warning_modal">When Browsing (`user_has_browsed`),<br> show Warning Modal</button>
438
+ </td>
439
+ <td class="action-button">
440
+ <button id="show_expired_modal">While Browsing (`user_has_browsed`),<br> Expire book & show its modal</button>
441
+ </td>
442
+ <td class="action-button">
443
+ <button id="resync_timer">While Browsing (`user_has_browsed`),<br> resync timer</button>
444
+ </td>
445
+ </tr>
446
+ </tbody></table>
447
+ </fieldset>
448
+ </td>
449
+ </tr>
413
450
  </table>
414
451
  </fieldset>
415
452
  </div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@internetarchive/ia-book-actions",
3
- "version": "1.1.0-alpha14",
3
+ "version": "1.1.0-alpha15",
4
4
  "description": "Bookreader actions for borrowable book",
5
5
  "author": "Internet Archive",
6
6
  "license": "AGPL-3.0-only",
@@ -14,8 +14,8 @@
14
14
  "lint": "eslint --ext .js,.html . --ignore-path .gitignore && prettier \"**/*.js\" --check --ignore-path .gitignore",
15
15
  "format": "eslint --ext .js,.html . --fix --ignore-path .gitignore && prettier \"**/*.js\" --write --ignore-path .gitignore",
16
16
  "test": "yarn run lint && yarn test:fast",
17
- "test:fast": "web-test-runner --coverage",
18
- "test:watch": "web-test-runner --watch"
17
+ "test:fast": "web-test-runner --coverage --playwright --browsers chromium firefox webkit",
18
+ "test:watch": "web-test-runner --watch --playwright --browsers chromium firefox webkit"
19
19
  },
20
20
  "dependencies": {
21
21
  "@internetarchive/icon-info": "^1.2.6",
@@ -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: 155,
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"
@@ -59,6 +59,7 @@ export default class IABookActions extends LitElement {
59
59
  disableActionGroup: { type: Boolean },
60
60
  modal: { Object },
61
61
  tokenDelay: { type: Number },
62
+ timerExecutionSeconds: { type: Number },
62
63
  localCache: { type: Object },
63
64
  loanRenewTimeConfig: { type: Object },
64
65
  loanRenewResult: { type: Object },
@@ -79,6 +80,7 @@ export default class IABookActions extends LitElement {
79
80
  this.sharedObserver = undefined;
80
81
  this.disableActionGroup = false;
81
82
  this.tokenDelay = 120; // in seconds
83
+ this.timerExecutionSeconds = 30;
82
84
 
83
85
  // private props
84
86
  this.postInitComplete = false;
@@ -89,10 +91,6 @@ export default class IABookActions extends LitElement {
89
91
  this.lendingOptions = {};
90
92
  this.borrowType = null; // 'browsed'|'borrowed'
91
93
  this.browseTimer = undefined; // timeout
92
- this.timerExecutionSeconds = 10;
93
-
94
- /** @deprecated */
95
- this.toast = undefined;
96
94
 
97
95
  /**
98
96
  * when user click on [return the book] button on warning modal
@@ -117,7 +115,7 @@ export default class IABookActions extends LitElement {
117
115
  * contains one hour auto-loan-renew response
118
116
  *
119
117
  * @type {object} loanRenewResult
120
- * @property {string} texts - texts messages need to show in toast message
118
+ * @property {string} texts - texts messages shows in modal
121
119
  * @property {boolean} renewNow - key to determine if need to renew now
122
120
  * @property {number} secondsLeft - seconds left in active loan
123
121
  */
@@ -132,10 +130,13 @@ export default class IABookActions extends LitElement {
132
130
  this.disconnectResizeObserver();
133
131
  }
134
132
 
133
+ /**
134
+ * send log messages to sentry
135
+ * @param {string} msg
136
+ */
135
137
  sentryCaptureMsg(msg) {
136
138
  log(window?.Sentry);
137
- log(window?.Sentry?.captureMessage(msg));
138
- if (window?.Sentry) window?.Sentry?.captureMessage(msg);
139
+ window?.Sentry?.captureMessage(msg);
139
140
  }
140
141
 
141
142
  firstUpdated() {
@@ -291,19 +292,31 @@ export default class IABookActions extends LitElement {
291
292
 
292
293
  /**
293
294
  * auto-renew interval may stale when tab is in background,
294
- * when user jump on the books tab,
295
+ * so when user open that tab again,
295
296
  * the [visibilitychange] event can trigger timer to show relavent messages
296
297
  */
297
- document.addEventListener("visibilitychange", async () => {
298
+ document.addEventListener('visibilitychange', async () => {
298
299
  if (!document.hidden) {
299
- log('visibilitychange is not hidden time: ------------------ ', new Date().getMinutes(), new Date().getSeconds());
300
-
301
- if (this.borrowType === 'browsed' && this.lendingStatus.browsingExpired === false) {
302
- const loanTime = await this.localCache.get(`${this.identifier}-loanTime`);
303
- const secondsLeft = Math.round((loanTime - new Date()) / 1000); // different in seconds
300
+ log(
301
+ 'visibilitychange event execute:------------------ ',
302
+ new Date().getMinutes(),
303
+ new Date().getSeconds(),
304
+ this.borrowType
305
+ );
306
+
307
+ if (
308
+ this.borrowType === 'browsed' &&
309
+ this.lendingStatus.browsingExpired === false
310
+ ) {
311
+ const loanTime = await this.localCache.get(
312
+ `${this.identifier}-loanTime`
313
+ );
314
+
315
+ // number of seconds left in current loan
316
+ const secondsLeft = Math.round((loanTime - new Date()) / 1000);
304
317
 
305
318
  if (secondsLeft >= this.timerExecutionSeconds) {
306
- this.setIntervalExecutionBlock(Number(secondsLeft));
319
+ this.loanStatusCheckInterval(Number(secondsLeft));
307
320
  } else {
308
321
  this.browseHasExpired();
309
322
  this.disconnectedCallback();
@@ -490,31 +503,6 @@ export default class IABookActions extends LitElement {
490
503
  await this.modal?.showModal({ config, customModalContent });
491
504
  }
492
505
 
493
- /**
494
- * @deprecated
495
- * create / select the toast-template component on DOM
496
- *
497
- * @memberof IABookActions
498
- */
499
- async useToastManager() {
500
- if (!this.toast) {
501
- this.toast = this.shadowRoot.querySelector('toast-template');
502
-
503
- if (!this.toast) this.toast = document.createElement('toast-template');
504
- }
505
-
506
- await this.shadowRoot.appendChild(this.toast);
507
- }
508
-
509
- /**
510
- * @deprecated
511
- * close/hide toast message
512
- */
513
- async closeToastManager() {
514
- this.toast = this.shadowRoot.querySelector('toast-template');
515
- this.toast?.remove();
516
- }
517
-
518
506
  /**
519
507
  * Execute when loan is expired
520
508
  */
@@ -643,7 +631,10 @@ export default class IABookActions extends LitElement {
643
631
  if (this.loanRenewResult.renewNow) {
644
632
  // Now, let's reset loan duration & this.lendingStatus
645
633
  const loanTime = await this.localCache.get(`${this.identifier}-loanTime`);
646
- const secondsLeft = Math.round((loanTime - new Date()) / 1000); // different in seconds
634
+
635
+ // number of seconds left in current loan
636
+ const secondsLeft = Math.round((loanTime - new Date()) / 1000);
637
+ log(loanTime, secondsLeft);
647
638
 
648
639
  log('IABookActions: handleLoanAutoRenewed --- ', {
649
640
  ajaxResponse: detail?.data,
@@ -680,7 +671,7 @@ export default class IABookActions extends LitElement {
680
671
 
681
672
  window.IALendingIntervals.timerCountdown = setInterval(async () => {
682
673
  // interval execution block
683
- await this.setIntervalExecutionBlock(secondsLeft);
674
+ await this.loanStatusCheckInterval(secondsLeft);
684
675
  }, this.timerExecutionSeconds * 1000);
685
676
  }
686
677
 
@@ -689,10 +680,10 @@ export default class IABookActions extends LitElement {
689
680
  * - if setInterval timer gone off, let's resync
690
681
  * - loan-renew attempt to renew the loan OR show relavant message
691
682
  * - if loan has expired, clear interval timers
692
- *
693
- * @param {Number} secondsLeftOnLoan
683
+ *
684
+ * @param {Number} secondsLeftOnLoan
694
685
  */
695
- async setIntervalExecutionBlock(secondsLeftOnLoan) {
686
+ async loanStatusCheckInterval(secondsLeftOnLoan) {
696
687
  let secondsLeft = secondsLeftOnLoan;
697
688
  secondsLeft -= this.timerExecutionSeconds;
698
689
  secondsLeft = Math.round(secondsLeft); // round number
@@ -702,21 +693,30 @@ export default class IABookActions extends LitElement {
702
693
  // if really updated, escape from here
703
694
  const resyncd = await this.reSyncTimerIfGoneOff(secondsLeft);
704
695
 
705
- if (resyncd?.hasSynced) {
696
+ if (resyncd.hasSynced) {
706
697
  secondsLeft = resyncd.whatShouldLeft;
707
698
  log('startTimerCountdown --- stale, timer has resyncd', {
708
699
  secondsLeft,
709
700
  });
710
701
  }
711
702
 
712
- log('startTimerCountdown --- countdown still valid. continue...', {
713
- resyncd,
714
- secondsLeft,
715
- loanRenewAtLast: this.loanRenewTimeConfig.loanRenewAtLast,
716
- }, 'time: ', new Date().getMinutes(), ':', new Date().getSeconds(), ', timerDelay: ', this.timerExecutionSeconds);
703
+ log(
704
+ 'startTimerCountdown --- countdown still valid. continue...',
705
+ {
706
+ resyncd,
707
+ secondsLeft,
708
+ loanRenewAtLast: this.loanRenewTimeConfig.loanRenewAtLast,
709
+ },
710
+ 'time: ',
711
+ new Date().getMinutes(),
712
+ ':',
713
+ new Date().getSeconds(),
714
+ ', timerDelay: ',
715
+ this.timerExecutionSeconds
716
+ );
717
717
 
718
718
  /**
719
- * execute from last 10th minute to 0th minute
719
+ * execute from last 10th minutes to 0th minute
720
720
  * - 10th - to check if user has viewed
721
721
  * - till 0th - to show warning msg with remaining time to auto expired
722
722
  * @see IABookActions::bindLoanRenewEvents
@@ -736,8 +736,7 @@ export default class IABookActions extends LitElement {
736
736
  * helper function to determine if timer is not in sync properly
737
737
  *
738
738
  * @param {number} timerSecondsLeft - actual seconds left get from setInterval
739
- *
740
- * @memberof TimerCountdown
739
+ * @returns {Object} { hasSynced: [boolean], whatShouldLeft: [number] }
741
740
  */
742
741
  async reSyncTimerIfGoneOff(timerSecondsLeft) {
743
742
  const currentTime = new Date();
@@ -772,10 +771,10 @@ export default class IABookActions extends LitElement {
772
771
 
773
772
  if (whatIsleft !== whatShouldLeft) {
774
773
  log(`reSyncTimerIfGoneOff --- let's re-sync.`);
775
-
776
774
  return { hasSynced: true, whatShouldLeft };
777
775
  }
778
- return false;
776
+
777
+ return { hasSynced: false, whatShouldLeft };
779
778
  }
780
779
 
781
780
  /**
@@ -987,19 +986,11 @@ export default class IABookActions extends LitElement {
987
986
  .lending-wrapper {
988
987
  width: 100%;
989
988
  margin: 0 auto;
990
- background: var(--primaryBGColor, #000);
991
- color: var(--primaryTextColor, #fff);
992
989
  display: inline-flex;
993
990
  align-items: center;
994
991
  justify-content: center;
995
992
  flex-wrap: wrap;
996
993
  }
997
-
998
- toast-template {
999
- --toastTopMargin: 80px;
1000
- --toastBGColor: #333;
1001
- --toastFontColor: #fff;
1002
- }
1003
994
  `;
1004
995
  }
1005
996
  }
@@ -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();