@internetarchive/ia-book-actions 0.1.9-alpha2 → 0.1.9-alpha4

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
@@ -131,7 +131,7 @@
131
131
  iaBookActions.returnUrl = '';
132
132
  iaBookActions.localCache = localCache;
133
133
 
134
- let loanTotalTime2Min = { loanTotalTime: 10, loanRenewAtLast: 5, pageChangedInLast: 8 };
134
+ let loanTotalTime2Min = { loanTotalTime: 65, loanRenewAtLast: 60, pageChangedInLast: 15 };
135
135
  let loanTotalTime5Min = { loanTotalTime: 300, loanRenewAtLast: 180, pageChangedInLast: 15 };
136
136
  let loanTotalTime1Hour = { loanTotalTime: 3600, loanRenewAtLast: 660, pageChangedInLast: 900 };
137
137
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@internetarchive/ia-book-actions",
3
- "version": "0.1.9-alpha2",
3
+ "version": "0.1.9-alpha4",
4
4
  "description": "Bookreader actions for borrowable book",
5
5
  "author": "Internet Archive",
6
6
  "license": "AGPL-3.0-only",
@@ -18,6 +18,7 @@
18
18
  "test:watch": "web-test-runner --watch --playwright --browsers chromium firefox webkit"
19
19
  },
20
20
  "dependencies": {
21
+ "@internetarchive/ia-activity-indicator": "^0.0.4",
21
22
  "@internetarchive/icon-info": "^1.2.6",
22
23
  "@internetarchive/local-cache": "^0.2.1",
23
24
  "@internetarchive/modal-manager": "^0.2.12",
@@ -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: 3591,
121
+ secondsLeftOnLoan: 155,
122
122
  shouldProtectImages: true,
123
123
  totalWaitlistCount: 0,
124
124
  userHasBorrowed: false,
@@ -30,6 +30,7 @@ export class CollapsibleActionGroup extends ActionsHandler {
30
30
  returnUrl: { type: String },
31
31
  autoRenew: { type: Boolean },
32
32
  autoReturn: { type: Boolean },
33
+ returnNow: { type: Boolean },
33
34
  };
34
35
  }
35
36
 
@@ -51,6 +52,7 @@ export class CollapsibleActionGroup extends ActionsHandler {
51
52
  this.returnUrl = '';
52
53
  this.autoRenew = false;
53
54
  this.autoReturn = false;
55
+ this.returnNow = false;
54
56
  }
55
57
 
56
58
  updated(changed) {
@@ -68,18 +70,22 @@ export class CollapsibleActionGroup extends ActionsHandler {
68
70
  if (changed.has('autoReturn') && this.autoReturn === true) {
69
71
  this.dispatchLoanEvent('autoReturn');
70
72
  }
73
+
74
+ if (changed.has('returnNow') && this.returnNow === true) {
75
+ this.dispatchLoanEvent('returnNow', { borrowType: 'browse' });
76
+ }
71
77
  }
72
78
 
73
79
  /**
74
- * dispatch event when book is auto renewed / returned
80
+ * dispatch event when book is auto auto-renewed / auto-returned / returned
75
81
  * listen these events in action-handler.js to execute ajax call on petabox.
76
82
  * @see ActionsHandler
77
83
  *
78
- * @param {string} event - autoRenew|autoReturn
84
+ * @param {string} event - autoRenew|autoReturn|returnNow
79
85
  * @memberof CollapsibleActionGroup
80
86
  */
81
- dispatchLoanEvent(event) {
82
- this.dispatchEvent(new Event(event));
87
+ dispatchLoanEvent(event, detail) {
88
+ this.dispatchEvent(new CustomEvent(event, { detail }));
83
89
  }
84
90
 
85
91
  /**
@@ -41,7 +41,7 @@ export default class TimerCountdown extends LitElement {
41
41
  * delay seconds in setInterval function
42
42
  * @type {number}
43
43
  */
44
- this.timerExecutionSeconds = 60;
44
+ this.timerExecutionSeconds = 30;
45
45
  }
46
46
 
47
47
  disconnectedCallback() {
@@ -66,6 +66,7 @@ export default class TimerCountdown extends LitElement {
66
66
  // store current time
67
67
  this.timeWhenTimerStart = new Date();
68
68
 
69
+ // this.dispatchLoanRenewEvent(secondsLeft);
69
70
  /**
70
71
  * set interval in window object
71
72
  * @see ia-lending-intervals.js
@@ -39,7 +39,7 @@ export class LoanRenewHelper {
39
39
  }
40
40
 
41
41
  /**
42
- * Trigger this function when user has browsed a book and change the post
42
+ * Trigger this function when user has browsed a book and change the page
43
43
  * - every time user change the page, we set current time in indexedDB
44
44
  * - also check if need to auto renew current loan
45
45
  *
@@ -1,4 +1,5 @@
1
1
  /* eslint-disable camelcase */
2
+ /* eslint-disable class-methods-use-this */
2
3
  import { html, css, LitElement, nothing } from 'lit';
3
4
 
4
5
  import { SharedResizeObserver } from '@internetarchive/shared-resize-observer';
@@ -23,11 +24,17 @@ export const events = {
23
24
  browseExpired: 'IABookReader:BrowsingHasExpired',
24
25
  };
25
26
 
27
+ /**
28
+ * custom styling for modal-manager buttons
29
+ * TODO: lets allow modal-manager to know ia-button classes
30
+ */
26
31
  export const modalButtonStyle = {
27
32
  iaButton:
28
- 'min-height:3rem;cursor:pointer;color:white;border-radius:0.4rem;border:1px solid transparent;padding:4px 8px;',
29
- renew: 'background:#194880;border:1px solid #c5d1df;',
30
- return: 'background:#d9534f;color:white;border:1px solid #c5d1df;',
33
+ 'min-height:3.5rem;cursor:pointer;color:white;border-radius:0.4rem;border:1px solid #c5d1df;padding:4px 8px;width:auto;user-select:none;',
34
+ renew: 'background:#194880;width:110px;',
35
+ return: 'background:#d9534f;width:120px;',
36
+ loaderIcon:
37
+ 'display:inline-block;width:20px;height:20px;margin-top:2px;color:white;--activityIndicatorLoadingRingColor:#fff;--activityIndicatorLoadingDotColor:#fff;',
31
38
  refresh:
32
39
  'background:none;font-size:inherit;border:0;padding:0;color:#0000ee;cursor:pointer;text-decoration:underline',
33
40
  };
@@ -71,7 +78,6 @@ export default class IABookActions extends LitElement {
71
78
  this.sharedObserver = undefined;
72
79
  this.disableActionGroup = false;
73
80
  this.modal = undefined;
74
- this.toast = undefined;
75
81
  this.tokenDelay = 120; // in seconds
76
82
 
77
83
  // private props
@@ -81,10 +87,19 @@ export default class IABookActions extends LitElement {
81
87
  this.secondaryActions = [];
82
88
  this.lendingOptions = {};
83
89
  this.borrowType = ''; // (browsed|borrowed)
84
- this.consecutiveLoanCounts = 1; // consecutive loan count
90
+ this.browseTimer = undefined;
91
+
92
+ /** @deprecated */
93
+ this.toast = undefined;
94
+ /** @deprecated */
85
95
  this.suppressToast = false;
96
+ /** @deprecated */
86
97
  this.suppressAutoRenew = false;
87
- this.browseTimer = undefined;
98
+
99
+ /**
100
+ * when user click on [return the book] button on warning modal
101
+ */
102
+ this.returnNow = false;
88
103
 
89
104
  /**
90
105
  * contains one hour auto-loan-renew time configuration
@@ -258,19 +273,13 @@ export default class IABookActions extends LitElement {
258
273
  * Bind 1 hour loan auto renew event,
259
274
  * There are two events we want to use,
260
275
  * 1. BookReader:userAction - dispatched from bookreader side
261
- * 2. click event to refresh the page when book is auto expired
262
- * 3. IABookActions:loanRenew - dispatched from timer-countdown component
276
+ * 2. IABookActions:loanRenew - dispatched from timer-countdown component
263
277
  */
264
278
  bindLoanRenewEvents() {
265
279
  /**
266
280
  * dispatched this event from bookreader page changed
267
281
  */
268
282
  window.addEventListener('BookReader:userAction', () => {
269
- console.log('** BR user Action', {
270
- suppressAutoRenew: this.suppressAutoRenew,
271
- borrowType: this.borrowType,
272
- });
273
-
274
283
  this.suppressToast = true;
275
284
  this.closeToastManager();
276
285
 
@@ -283,32 +292,13 @@ export default class IABookActions extends LitElement {
283
292
  }
284
293
  });
285
294
 
286
- /**
287
- * detect click-event on document to close toast-template
288
- */
289
- document.addEventListener('click', e => {
290
- console.log('*** top level click handler to remove toast on click');
291
- if (this.loanRenewHelper && this.loanRenewResult.secondsLeft > 0) {
292
- this.suppressToast = true;
293
- }
294
-
295
- // reload when user click on book-page and book is EXPIRED
296
- const hasExpired =
297
- 'browsingExpired' in this.lendingStatus &&
298
- this.lendingStatus?.browsingExpired;
299
- if (hasExpired && e.target.nodeName !== 'IA-BOOK-ACTIONS') {
300
- // window.location?.reload();
301
- }
302
- });
303
-
304
295
  /**
305
296
  * this event dispatched from timer-countdown component for:
306
- * 1. user turned book page after showing auto-returned warning message
307
- * 2. show warning message having remaining time
297
+ * 1. user turned book page after showing auto-returned warning modal
298
+ * 2. show warning modal having remaining time
308
299
  * @see TimerCountdown::timerCountdown
309
300
  */
310
301
  this.addEventListener('IABookActions:loanRenew', async event => {
311
- console.log('** IABookActions:loanRenew', event.detail);
312
302
  await this.autoLoanRenewChecker(false);
313
303
 
314
304
  /**
@@ -324,18 +314,16 @@ export default class IABookActions extends LitElement {
324
314
  this.browseHasExpired();
325
315
  }
326
316
 
327
- // show warning message with remaining time to auto returned it.
317
+ // show warning modal with remaining time to auto returned it.
328
318
  if (this.loanRenewResult.renewNow === false) {
329
319
  /**
330
320
  * so compensate for the 50 second buffer to handle above race conditions
331
321
  * let's reduce 1 min from warning texts and early return the book when 1 min left.
332
322
  */
333
323
  secondsLeft -= 60;
334
-
335
324
  this.loanRenewResult.secondsLeft = secondsLeft;
336
325
 
337
- // this.showToastMessage();
338
- this.showModalManager();
326
+ this.showWarningModal();
339
327
  }
340
328
  });
341
329
  }
@@ -360,7 +348,6 @@ export default class IABookActions extends LitElement {
360
348
 
361
349
  /**
362
350
  * create / select the modal-manager component on DOM
363
- *
364
351
  * @memberof IABookActions
365
352
  */
366
353
  async useModalManager() {
@@ -374,9 +361,10 @@ export default class IABookActions extends LitElement {
374
361
  await document.body.appendChild(this.modal);
375
362
  }
376
363
 
377
- async showModalManager(type = '') {
378
- if (this.suppressToast) return;
379
-
364
+ /**
365
+ * Show the waring modal to ask user if they are still reading
366
+ */
367
+ async showWarningModal() {
380
368
  await this.useModalManager();
381
369
 
382
370
  // if secondsLeft < 60, consider it 1 minute
@@ -387,57 +375,89 @@ export default class IABookActions extends LitElement {
387
375
  secondsLeft = secondsLeft > 60 ? secondsLeft : 60;
388
376
  }
389
377
 
390
- let customContent = '';
391
-
392
378
  const config = new ModalConfig();
393
-
394
379
  config.headline = 'Are you still here?';
395
380
  config.showCloseButton = false;
396
381
  config.closeOnBackdropClick = false;
382
+ config.message = this.loanRenewHelper?.getMessageTexts(
383
+ this.loanRenewResult.texts,
384
+ secondsLeft
385
+ );
397
386
 
398
- if (type === 'expired') {
399
- config.message = 'This book has been returned due to inactivity.';
400
- customContent = html`<br />
401
- <div style="text-align: center">
402
- <button
403
- style="${modalButtonStyle.iaButton} ${modalButtonStyle.renew}"
404
- @click=${() => {
405
- window.location?.reload();
406
- }}
407
- >
408
- Okay
409
- </button>
410
- </div> `;
411
- } else {
412
- config.message = this.loanRenewHelper?.getMessageTexts(
413
- this.loanRenewResult.texts,
414
- secondsLeft
415
- );
416
- customContent = html`<br />
417
- <div style="text-align: center">
418
- <button
419
- style="${modalButtonStyle.iaButton} ${modalButtonStyle.renew}"
420
- @click=${() => {
421
- this.autoLoanRenewChecker(true);
422
- }}
423
- >
424
- Keep reading
425
- </button>
426
- <button
427
- style="${modalButtonStyle.iaButton} ${modalButtonStyle.return}"
428
- @click=${() => {
429
- this.dispatchEvent(new CustomEvent('returnNow', {}));
430
- // this.browseHasExpired();
431
- }}
432
- >
433
- Return the book
434
- </button>
435
- </div> `;
436
- }
387
+ const customContent = html`<br />
388
+ <div id="custom-buttons" style="display:flex;justify-content:center;">
389
+ <button
390
+ style="${modalButtonStyle.iaButton} ${modalButtonStyle.renew}"
391
+ @click=${async event => {
392
+ this.changeModalState(event);
393
+ this.autoLoanRenewChecker(true);
394
+ }}
395
+ >
396
+ Keep reading
397
+ </button>
398
+ <button
399
+ style="${modalButtonStyle.iaButton} ${modalButtonStyle.return}"
400
+ @click=${async event => {
401
+ this.changeModalState(event);
402
+ document.querySelector('ia-book-actions').disableActionGroup = true;
403
+ this.returnNow = true;
404
+ }}
405
+ >
406
+ Return the book
407
+ </button>
408
+ </div> `;
437
409
 
438
410
  this.modal.showModal({ config, customModalContent: customContent });
439
411
  }
440
412
 
413
+ /**
414
+ * helper function to change button click states on modal-manager
415
+ * - add loader on button click
416
+ * - change button appearance
417
+ *
418
+ * @param {Event} event
419
+ */
420
+ changeModalState(event) {
421
+ if (event.target === undefined) return;
422
+
423
+ const button = event.target;
424
+ button.innerHTML = `<ia-activity-indicator
425
+ mode="processing"
426
+ style=${modalButtonStyle.loaderIcon}
427
+ ></ia-activity-indicator>`;
428
+
429
+ const parentElement = event.target.parentNode;
430
+ parentElement.style.pointerEvents = 'none';
431
+ parentElement.style.opacity = 0.8;
432
+ }
433
+
434
+ /**
435
+ * Show modal when book is auto returned
436
+ */
437
+ async showExpiredModal() {
438
+ await this.useModalManager();
439
+
440
+ const config = new ModalConfig();
441
+ config.headline = '';
442
+ config.showCloseButton = false;
443
+ config.closeOnBackdropClick = false;
444
+ config.message = 'This book has been returned due to inactivity.';
445
+
446
+ const customContent = html`<br />
447
+ <div style="text-align: center">
448
+ <button
449
+ style="${modalButtonStyle.iaButton} ${modalButtonStyle.renew}"
450
+ @click=${() => {
451
+ window.location?.reload();
452
+ }}
453
+ >
454
+ Okay
455
+ </button>
456
+ </div> `;
457
+
458
+ this.modal?.showModal({ config, customModalContent: customContent });
459
+ }
460
+
441
461
  /**
442
462
  * close/hide modal-manager
443
463
  */
@@ -560,11 +580,9 @@ export default class IABookActions extends LitElement {
560
580
  // show message after browsed book is expired.
561
581
  this.loanRenewResult.renewNow = false;
562
582
  this.loanRenewResult.texts =
563
- 'This book has been automatically returned due to inactivity.';
583
+ 'This book has been returned due to inactivity.';
564
584
 
565
- // this.suppressToast = false;
566
- // this.showToastMessage();
567
- this.showModalManager('expired');
585
+ this.showExpiredModal();
568
586
 
569
587
  window?.Sentry?.captureMessage(sentryLogs.browseHasExpired);
570
588
  }
@@ -642,6 +660,7 @@ export default class IABookActions extends LitElement {
642
660
  ?disabled=${this.disableActionGroup}
643
661
  ?autoRenew=${this.loanRenewResult.renewNow}
644
662
  ?autoReturn=${this.lendingStatus.browsingExpired}
663
+ ?returnNow=${this.returnNow}
645
664
  @loanAutoRenewed=${this.handleLoanAutoRenewed}
646
665
  @lendingActionError=${this.handleLendingActionError}
647
666
  @toggleActionGroup=${this.handleToggleActionGroup}
@@ -665,11 +684,11 @@ export default class IABookActions extends LitElement {
665
684
 
666
685
  this.suppressToast = false;
667
686
  await this.browseHasRenew();
668
- // await this.showToastMessage();
669
687
  await this.resetTimerCountState();
670
688
 
671
689
  // close the modal
672
- await this.modal?.closeModal();
690
+ this.modal?.remove();
691
+ this.modal?.showModal({ config: {}, customModalContent: `` });
673
692
 
674
693
  window?.Sentry?.captureMessage(sentryLogs.bookHasRenewed);
675
694
 
@@ -723,7 +742,7 @@ export default class IABookActions extends LitElement {
723
742
  this.disableActionGroup = !this.disableActionGroup;
724
743
  }
725
744
 
726
- /*
745
+ /**
727
746
  * handle lending errors occure during different operation like
728
747
  * browse book, borrow book, borrowed book token etc...
729
748
  *
@@ -80,7 +80,7 @@ describe('Loan Renew Determine', () => {
80
80
  );
81
81
 
82
82
  expect(toastMsg).to.be.equal(
83
- 'This book will be automatically returned in 2 minutes unless you turn a page.'
83
+ 'With no action, this book will be auto-returned in 2 minutes.'
84
84
  );
85
85
  });
86
86
  });
@@ -188,7 +188,9 @@ describe('Borrow status actions', () => {
188
188
  },
189
189
  ];
190
190
 
191
- expect(el.primaryTitle).to.equal('');
191
+ expect(el.primaryTitle).to.equal(
192
+ 'Renews automatically as long as pages are turned.'
193
+ );
192
194
  expect(el.primaryActions.length).to.equal(2);
193
195
  expect(el.primaryActions[0].text).to.equal('Log In and Borrow');
194
196
  expect(el.primaryActions[0].text).to.equal(expectedPrimaryActions[0].text);
@@ -274,7 +276,7 @@ describe('Browsing expired status', () => {
274
276
  expect(el.loanRenewResult.renewNow).to.equal(false);
275
277
  expect(el.loanRenewResult.secondsLeft).to.equal(0);
276
278
  expect(el.loanRenewResult.texts).to.equal(
277
- 'This book has been automatically returned due to inactivity.'
279
+ 'This book has been returned due to inactivity.'
278
280
  );
279
281
  });
280
282