@internetarchive/ia-book-actions 0.1.11-b4 → 0.1.11-b41

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,11 +131,11 @@
131
131
  iaBookActions.returnUrl = '';
132
132
  iaBookActions.localCache = localCache;
133
133
 
134
- let loanTotalTime2Min = { loanTotalTime: 65, loanRenewAtLast: 60, pageChangedInLast: 15 };
134
+ let loanTotalTime2Min = { loanTotalTime: 65, loanRenewAtLast: 60, pageChangedInLast: 30 };
135
135
  let loanTotalTime5Min = { loanTotalTime: 300, loanRenewAtLast: 180, pageChangedInLast: 15 };
136
136
  let loanTotalTime1Hour = { loanTotalTime: 3600, loanRenewAtLast: 660, pageChangedInLast: 900 };
137
137
 
138
- iaBookActions.loanRenewTimeConfig = loanTotalTime2Min;
138
+ iaBookActions.loanRenewTimeConfig = loanTotalTime5Min;
139
139
 
140
140
  let lendingStatus1 = iaBookActions.lendingStatus;
141
141
  let secondsLeftOnLoan = iaBookActions.loanRenewTimeConfig.loanTotalTime;
@@ -242,6 +242,31 @@
242
242
  });
243
243
  });
244
244
 
245
+ document.querySelector('#show_warning_modal').addEventListener('click', async () => {
246
+ if (!iaBookActions.lendingStatus.user_has_browsed) {
247
+ const newLendingStatus = { ...iaBookActions.lendingStatus, user_has_browsed: true };
248
+ iaBookActions.lendingStatus = newLendingStatus;
249
+ await iaBookActions.updateComplete;
250
+ }
251
+
252
+ iaBookActions.showWarningModal();
253
+
254
+ if (iaBookActions.lendingStatus.user_has_browsed) {
255
+ iaBookActions.showWarningModal();
256
+ }
257
+ });
258
+
259
+ document.querySelector('#show_expired_modal').addEventListener('click', async () => {
260
+ if (!iaBookActions.lendingStatus.user_has_browsed) {
261
+ const newLendingStatus = { ...iaBookActions.lendingStatus, user_has_browsed: true };
262
+ iaBookActions.lendingStatus = newLendingStatus;
263
+ await iaBookActions.updateComplete;
264
+ }
265
+ await new Promise(resolve => setTimeout(resolve, 5000));
266
+
267
+ iaBookActions.browseHasExpired();
268
+ });
269
+
245
270
  document.querySelectorAll('.availableToBorrow input[type=radio]').forEach((item) => {
246
271
  item.addEventListener('click', (e) => {
247
272
  if (e.target.value === 'available_to_borrow') {
@@ -334,6 +359,13 @@
334
359
  <label><input type="radio" name="browse" value="available_to_browse" />available_to_browse</label>
335
360
  <label><input type="radio" name="browse" value="user_has_browsed" />user_has_browsed</label>
336
361
  <label><input type="radio" name="browse" value="browsingExpired" />browsingExpired</label>
362
+ <div>
363
+ <button id="show_warning_modal">When Browsing (`user_has_browsed`), show Warning Modal</button>
364
+ </div>
365
+ <div>
366
+ <button id="show_expired_modal">While Browsing (`user_has_browsed`), Expire book & show its modal</button>
367
+ how_expired_modal
368
+ </div>
337
369
  </td>
338
370
  <td class="availableToBorrow">
339
371
  <label><input type="radio" name="borrow" value="available_to_borrow" />available_to_borrow</label>
@@ -356,5 +388,6 @@
356
388
  </table>
357
389
  </fieldset>
358
390
  </div>
391
+ <modal-manager></modal-manager>
359
392
  </body>
360
393
  </html>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@internetarchive/ia-book-actions",
3
- "version": "0.1.11-b4",
3
+ "version": "0.1.11-b41",
4
4
  "description": "Bookreader actions for borrowable book",
5
5
  "author": "Internet Archive",
6
6
  "license": "AGPL-3.0-only",
@@ -18,7 +18,6 @@
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",
22
21
  "@internetarchive/icon-info": "^1.2.6",
23
22
  "@internetarchive/local-cache": "^0.2.1",
24
23
  "@internetarchive/modal-manager": "^0.2.12",
@@ -3,32 +3,43 @@ import { html, css, LitElement } from 'lit';
3
3
  export default class TimerCountdown extends LitElement {
4
4
  static get properties() {
5
5
  return {
6
- secondsLeftOnLoan: { type: Number }, // in seconds
6
+ secondsLeftOnLoan: { type: Number },
7
+ displayTime: { type: Boolean },
7
8
  };
8
9
  }
9
10
 
10
11
  constructor() {
11
12
  super();
12
13
 
13
- /**
14
- * seconds left in current loan
15
- * @type {number}
16
- */
17
14
  this.secondsLeftOnLoan = 0;
15
+ this.displayTime = false;
16
+ }
17
+
18
+ get minutesLeftOnLoan() {
19
+ let timeLeft = Math.round(this.secondsLeftOnLoan);
20
+
21
+ // convert time from second to minute
22
+ timeLeft = Math.ceil(timeLeft / 60);
23
+
24
+ if (timeLeft < 10) {
25
+ timeLeft = `0:0${timeLeft}`;
26
+ } else if (timeLeft === 60) {
27
+ timeLeft = `1:00`;
28
+ } else {
29
+ timeLeft = `0:${timeLeft}`;
30
+ }
31
+ return timeLeft;
18
32
  }
19
33
 
20
34
  /**
21
35
  * get remaining time with timeunit
22
36
  *
23
37
  * @memberof TimerCountdown
24
- * @return string
38
+ * @return string - seconds left
25
39
  */
26
40
  get remainingTime() {
27
41
  const unitOfTime = 'minute';
28
- let timeLeft = Math.round(this.secondsLeftOnLoan);
29
-
30
- // convert time from second to minute
31
- timeLeft = Math.ceil(timeLeft / 60);
42
+ const timeLeft = this.minutesLeftOnLoan;
32
43
 
33
44
  return timeLeft !== 1
34
45
  ? `${timeLeft} ${unitOfTime}s`
@@ -36,58 +47,28 @@ export default class TimerCountdown extends LitElement {
36
47
  }
37
48
 
38
49
  render() {
50
+ const viewClass = this.displayTime ? 'view' : 'hide';
39
51
  return html`
40
- <svg
41
- width="14"
42
- height="14"
43
- viewBox="0 0 100 100"
44
- xmlns="http://www.w3.org/2000/svg"
52
+ <button
53
+ id="timer-counter"
54
+ class=${viewClass}
55
+ @click=${() => {
56
+ this.displayTime = !this.displayTime;
57
+ }}
58
+ role="timer"
45
59
  >
46
- <circle class="circle" cx="50" cy="50" r="50" />
47
- </svg>
48
- <span class="sr-only">${this.remainingTime} left</span>
60
+ <span>${this.minutesLeftOnLoan}</span>
61
+ <span class="sr-only">${this.remainingTime} left</span>
62
+ </button>
49
63
  `;
50
64
  }
51
65
 
52
66
  static get styles() {
53
- const white = css`var(--white, #fff)`;
54
- const timerSecondsLeft = css`var(--secondsLeftOnLoan, 6000s)`; //
55
- const timerStrokeLeft = css`var(--strokeLeftOnLoan, 315)`;
56
-
57
67
  return css`
58
68
  :host {
59
69
  right: 0;
60
70
  margin-right: 10px;
61
71
  position: absolute;
62
- height: 20px;
63
- width: 20px;
64
- }
65
-
66
- @keyframes circletimer {
67
- 0% {
68
- stroke-dashoffset: ${timerStrokeLeft};
69
- stroke-dasharray: 315;
70
- }
71
- 100% {
72
- stroke-dashoffset: 0;
73
- stroke-dasharray: 315;
74
- }
75
- }
76
-
77
- svg {
78
- background-color: ${white};
79
- border-radius: 50px;
80
- border: 2px solid ${white};
81
- transform: rotateZ(-90deg);
82
- }
83
-
84
- svg .circle {
85
- stroke: #000;
86
- stroke-width: 100px;
87
- fill: ${white};
88
- stroke-dashoffset: 315;
89
- stroke-dasharray: 0;
90
- animation: ${timerSecondsLeft} circletimer linear;
91
72
  }
92
73
 
93
74
  .sr-only {
@@ -100,6 +81,18 @@ export default class TimerCountdown extends LitElement {
100
81
  border: none;
101
82
  overflow: hidden;
102
83
  }
84
+
85
+ button#timer-counter {
86
+ cursor: pointer;
87
+ }
88
+
89
+ .hide {
90
+ opacity: 0;
91
+ }
92
+
93
+ .show {
94
+ opacity: 1;
95
+ }
103
96
  `;
104
97
  }
105
98
  }
@@ -6,4 +6,4 @@ export const mobileContainerWidth = 700;
6
6
  /**
7
7
  * shift the actions buttons in dropdown
8
8
  */
9
- export const tabletContainerWidth = 895;
9
+ export const tabletContainerWidth = 800;
@@ -1,3 +1,4 @@
1
+ /* global: window */
1
2
  /**
2
3
  * Collection of methods for managing intervals for lending system
3
4
  *
@@ -18,16 +19,19 @@ window.IALendingIntervals = {
18
19
  tokenPoller: 0,
19
20
 
20
21
  /**
21
- * store timer-countdown interval
22
- * @see TimerCountdown::timerCountdown
22
+ * renewal check interval
23
23
  */
24
24
  timerCountdown: 0,
25
25
 
26
+ /** expiration timer */
27
+ browseExpireTimeout: 0,
28
+
26
29
  /**
27
30
  * clear interval for create_token api
28
31
  */
29
32
  clearTokenPoller: () => {
30
- window.clearInterval(window?.IALendingIntervals?.tokenPoller);
33
+ window.clearInterval(window.IALendingIntervals.tokenPoller);
34
+ window.IALendingIntervals.tokenPoller = 0;
31
35
  },
32
36
 
33
37
  /**
@@ -35,13 +39,23 @@ window.IALendingIntervals = {
35
39
  */
36
40
  clearTimerCountdown: () => {
37
41
  window.clearInterval(window.IALendingIntervals.timerCountdown);
42
+ window.IALendingIntervals.timerCountdown = 0;
43
+ },
44
+
45
+ /**
46
+ * clear interval for graphic timer for one-hour loan renew
47
+ */
48
+ clearBrowseExpireTimeout: () => {
49
+ window.clearTimeout(window.IALendingIntervals.browseExpireTimeout);
50
+ window.IALendingIntervals.browseExpireTimeout = 0;
38
51
  },
39
52
 
40
53
  /**
41
54
  * clear all intervals being used acros lending system
42
55
  */
43
56
  clearAll: () => {
44
- window.clearInterval(window?.IALendingIntervals?.tokenPoller);
45
- window.clearInterval(window?.IALendingIntervals?.timerCountdown);
57
+ window?.IALendingIntervals?.clearTokenPoller();
58
+ window?.IALendingIntervals?.clearTimerCountdown();
59
+ window.IALendingIntervals.clearBrowseExpireTimeout();
46
60
  },
47
61
  };
@@ -21,7 +21,13 @@ export default async function ActionsHandlerService(options) {
21
21
  const tokenError = 'loan token not found. please try again later.';
22
22
  const borrowError =
23
23
  'This book is not available to borrow at this time. Please try again later.';
24
- const erroneousActions = ['browse_book', 'borrow_book', 'create_token'];
24
+ const erroneousActions = [
25
+ 'browse_book',
26
+ 'borrow_book',
27
+ 'create_token',
28
+ 'renew_loan',
29
+ 'return_loan',
30
+ ];
25
31
  const shouldReturnError =
26
32
  location?.href?.indexOf('?error=true') !== -1 &&
27
33
  location?.hostname !== 'archive.org';
@@ -36,7 +42,7 @@ export default async function ActionsHandlerService(options) {
36
42
  method: 'POST',
37
43
  body: formData,
38
44
  })
39
- .then(response => {
45
+ .then(async response => {
40
46
  // intentional error on localhost
41
47
  if (shouldReturnError && erroneousActions.includes(option?.action)) {
42
48
  return {
@@ -47,7 +53,12 @@ export default async function ActionsHandlerService(options) {
47
53
 
48
54
  // return success response for /demo/ server...
49
55
  if (baseHost == '/demo/1' || baseHost == '/demo/') {
50
- if (option?.action == 'renew_loan') {
56
+ if (
57
+ option?.action == 'renew_loan' ||
58
+ option?.action == 'return_loan'
59
+ ) {
60
+ // wait a few seconds so that the user can see the loading state
61
+ await new Promise(resolve => setTimeout(resolve, 5000));
51
62
  return {
52
63
  success: true,
53
64
  loan: { renewal: true },
@@ -2,10 +2,15 @@ import { LitElement } from 'lit';
2
2
 
3
3
  import { URLHelper } from '../../config/url-helper.js';
4
4
  import { sentryLogs } from '../../config/sentry-events.js';
5
+ import log from '../log.js';
5
6
 
6
7
  import ActionsHandlerService from './actions-handler-service.js';
7
8
  import LoanAnanlytics from '../loan-analytics.js';
8
9
  import * as Cookies from '../doc-cookies.js';
10
+ import {
11
+ analyticsCategories,
12
+ analyticsActions,
13
+ } from '../../config/analytics-event-and-category.js';
9
14
 
10
15
  /**
11
16
  * These are callback functions calling from actions-config.js file.
@@ -38,24 +43,53 @@ export default class ActionsHandler extends LitElement {
38
43
  }
39
44
 
40
45
  bindEvents() {
41
- this.addEventListener('browseBook', () => {
46
+ this.addEventListener('browseBook', async () => {
42
47
  this.handleBrowseIt();
43
- this.loanAnanlytics?.storeLoanStatsCount(this.identifier, 'browse');
48
+ await this.loanAnanlytics?.storeLoanStatsCount(this.identifier, 'browse');
49
+ this.loanAnanlytics?.sendEvent(
50
+ analyticsCategories.browse,
51
+ analyticsActions.browse
52
+ );
44
53
  });
45
54
 
46
- this.addEventListener('browseBookAgain', () => {
55
+ this.addEventListener('browseBookAgain', async () => {
47
56
  this.handleBrowseIt();
48
- this.loanAnanlytics?.storeLoanStatsCount(this.identifier, 'browseagain');
57
+ await this.loanAnanlytics?.storeLoanStatsCount(
58
+ this.identifier,
59
+ 'browseagain'
60
+ );
61
+ this.loanAnanlytics?.sendEvent(
62
+ analyticsCategories.browse,
63
+ analyticsActions.browseBookAgain
64
+ );
49
65
  });
50
66
 
51
- this.addEventListener('autoRenew', () => {
67
+ this.addEventListener('autoRenew', async () => {
52
68
  this.handleLoanRenewNow();
53
- this.loanAnanlytics?.storeLoanStatsCount(this.identifier, 'autorenew');
69
+ await this.loanAnanlytics?.storeLoanStatsCount(
70
+ this.identifier,
71
+ 'autorenew'
72
+ );
73
+ this.loanAnanlytics?.sendEvent(
74
+ analyticsCategories.browse,
75
+ analyticsActions.browseAutoRenew
76
+ );
54
77
  });
55
78
 
56
- this.addEventListener('autoReturn', () => {
79
+ this.addEventListener('autoReturn', async () => {
57
80
  this.handleReturnIt();
58
- this.loanAnanlytics?.storeLoanStatsCount(this.identifier, 'autoreturn');
81
+ await this.loanAnanlytics?.storeLoanStatsCount(
82
+ this.identifier,
83
+ 'autoreturn'
84
+ );
85
+ await this.loanAnanlytics?.storeLoanStatsCount(
86
+ this.identifier,
87
+ 'autorenew'
88
+ );
89
+ this.loanAnanlytics?.sendEvent(
90
+ analyticsCategories.browse,
91
+ analyticsActions.browseAutoReturn
92
+ );
59
93
  });
60
94
 
61
95
  this.addEventListener('returnNow', ({ detail }) => {
@@ -142,7 +176,7 @@ export default class ActionsHandler extends LitElement {
142
176
  action,
143
177
  identifier: this.identifier,
144
178
  success: data => {
145
- console.log("RENEW_LOAN --- ", data, action, data.loan, this.identifier);
179
+ log('RENEW_LOAN --- ', data, action, data.loan, this.identifier);
146
180
  if (data?.success === true && data?.loan?.renewal === true) {
147
181
  this.setBrowseTimeSession();
148
182
  this.dispatchEvent(
@@ -316,7 +350,7 @@ export default class ActionsHandler extends LitElement {
316
350
  // delete pageChangedTime when book is auto renew at nth minute
317
351
  await this.localCache.delete(`${this.identifier}-pageChangedTime`);
318
352
  } catch (error) {
319
- console.log(error);
353
+ log(error);
320
354
  }
321
355
  }
322
356
 
@@ -40,7 +40,7 @@ import ActionsConfig from './actions-config.js';
40
40
  * @enum {string}
41
41
  */
42
42
  export const bookTitles = {
43
- available_1hr: 'Renews automatically as long as pages are turned.',
43
+ available_1hr: 'Renews automatically with continued use.',
44
44
  available_14d: 'This book can be borrowed for 14 days.',
45
45
  available_pd: 'Book available to patrons with print disabilities.',
46
46
  available_waitlist: 'A waitlist is available.',
@@ -49,7 +49,7 @@ export const bookTitles = {
49
49
  being_borrowed: 'Another patron is using this book. Please check back later.',
50
50
  eligible_pd: 'You are eligible for print-disabled access.',
51
51
  on_waitlist: 'You are on the waitlist for this book.',
52
- session_expired: 'Renews automatically as long as pages are turned.',
52
+ session_expired: 'Renews automatically with continued use.',
53
53
  unavailable: 'This book is not available at this time.',
54
54
  };
55
55
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  import { analyticsCategories } from '../config/analytics-event-and-category.js';
4
4
  import * as Cookies from './doc-cookies.js';
5
+ import log from './log.js';
5
6
 
6
7
  /**
7
8
  * This class is used to send different GA events for loan system.
@@ -45,7 +46,7 @@ export default class LoanAnanlytics {
45
46
  '/'
46
47
  );
47
48
  } catch (error) {
48
- console.log(error);
49
+ log(error);
49
50
  this.sendEvent('Cookies-Error-Actions', error, this.identifier);
50
51
  }
51
52
  }
@@ -144,7 +145,7 @@ export default class LoanAnanlytics {
144
145
  * @memberof LoanAnanlytics
145
146
  */
146
147
  sendEvent(eventCategory, eventAction, label, extraParams) {
147
- // console.log(
148
+ // log(
148
149
  // 'eventCategory:-',
149
150
  // eventCategory,
150
151
  // '\t||\teventAction:-',
@@ -1,6 +1,7 @@
1
1
  /* eslint-disable class-methods-use-this */
2
2
 
3
3
  import { nothing } from 'lit';
4
+ import log from './log.js';
4
5
 
5
6
  /**
6
7
  * This class is used to determine if use is eligible for auto renew loan.
@@ -32,7 +33,7 @@ export class LoanRenewHelper {
32
33
  }
33
34
  return this.autoChecker(); // auto checker at last 10th minute
34
35
  } catch (error) {
35
- console.log(error);
36
+ log(error);
36
37
  }
37
38
 
38
39
  return nothing;
@@ -0,0 +1,16 @@
1
+ /* eslint-disable no-restricted-globals */
2
+ /* eslint-disable semi */
3
+ /**
4
+ * log() utility -- production ignores 'log()' JS calls; dev invokes 'log()'
5
+ */
6
+ const log =
7
+ location.hostname === 'localhost' ||
8
+ location.host.match(/^(www|cat)-[a-z0-9]+\.archive\.org$/) ||
9
+ location.host.match(/\.code\.archive\.org$/) ||
10
+ location.host.match(/\.dev\.archive\.org$/) ||
11
+ location.host.match(/^ia-petabox-/)
12
+ ? // eslint-disable-next-line no-console
13
+ console.log.bind(console) // convenient, no? Stateless function
14
+ : () => {};
15
+
16
+ export { log as default };
@@ -19,6 +19,7 @@ import { mobileContainerWidth } from './core/config/constants.js';
19
19
  import { sentryLogs } from './core/config/sentry-events.js';
20
20
  import { LoanTokenPoller } from './core/services/loan-token-poller.js';
21
21
  import { LoanRenewHelper } from './core/services/loan-renew-helper.js';
22
+ import log from './core/services/log.js';
22
23
 
23
24
  export const events = {
24
25
  browseExpired: 'IABookReader:BrowsingHasExpired',
@@ -70,14 +71,13 @@ export default class IABookActions extends LitElement {
70
71
  this.identifier = '';
71
72
  this.bookTitle = '';
72
73
  this.returnUrl = '';
73
- this.lendingStatus = {};
74
+ this.lendingStatus = {}; // very important as components feed from this
74
75
  this.width = 0;
75
76
  this.bwbPurchaseUrl = '';
76
77
  this.lendingBarPostInit = () => {};
77
78
  this.barType = 'action'; // 'title'|'action'
78
79
  this.sharedObserver = undefined;
79
80
  this.disableActionGroup = false;
80
- this.modal = undefined;
81
81
  this.tokenDelay = 120; // in seconds
82
82
 
83
83
  // private props
@@ -86,8 +86,8 @@ export default class IABookActions extends LitElement {
86
86
  this.primaryColor = 'primary';
87
87
  this.secondaryActions = [];
88
88
  this.lendingOptions = {};
89
- this.borrowType = null; // (browsed|borrowed)
90
- this.browseTimer = undefined;
89
+ this.borrowType = null; // 'browsed'|'borrowed'
90
+ this.browseTimer = undefined; // timeout
91
91
  this.timerExecutionSeconds = 30;
92
92
 
93
93
  /** @deprecated */
@@ -105,7 +105,7 @@ export default class IABookActions extends LitElement {
105
105
  * loanTotalTime: number, - total seconds a loan does have
106
106
  * loanRenewAtLast: number, - check for loan renew at last
107
107
  * pageChangedInLast: number, - consider loan renew eligible if viewed new page
108
- */
108
+ */
109
109
  this.loanRenewTimeConfig = {
110
110
  loanTotalTime: 3600, // 1 hour
111
111
  loanRenewAtLast: 660, // 11 minutes
@@ -121,12 +121,6 @@ export default class IABookActions extends LitElement {
121
121
  * @property {number} secondsLeft - seconds left in active loan
122
122
  */
123
123
  this.loanRenewResult = { texts: '', renewNow: false, secondsLeft: 0 };
124
-
125
- /**
126
- * need showdowRoot opened for resetTimerCountState
127
- * @see IABookActions::resetTimerCountState
128
- */
129
- this._shadowRoot = this.attachShadow({ mode: 'open' });
130
124
  }
131
125
 
132
126
  disconnectedCallback() {
@@ -155,7 +149,6 @@ export default class IABookActions extends LitElement {
155
149
  updated(changed) {
156
150
  if (changed.has('lendingStatus') || changed.has('bwbPurchaseUrl')) {
157
151
  this.setupLendingToolbarActions();
158
- this.update();
159
152
  }
160
153
 
161
154
  if (changed.has('sharedObserver')) {
@@ -254,14 +247,6 @@ export default class IABookActions extends LitElement {
254
247
  return;
255
248
  }
256
249
 
257
- // initial timer state for 1-hour loan
258
- if (this.borrowType === 'browsed' && !this.loanRenewResult.renewNow) {
259
- setTimeout(() => {
260
- console.log('IN TIMEOUT OF --- setupLendingToolbarActions, firing resetTimerCountState');
261
- this.resetTimerCountState();
262
- }, 100);
263
- }
264
-
265
250
  /**
266
251
  * tokenPoller determines if user has loan token for this book
267
252
  * - if book is going to renew, need to wait until renew is completed
@@ -269,6 +254,8 @@ export default class IABookActions extends LitElement {
269
254
  setTimeout(() => {
270
255
  if (!hasExpired) this.startLoanTokenPoller();
271
256
  }, 100);
257
+
258
+ this.requestUpdate();
272
259
  }
273
260
 
274
261
  /**
@@ -282,6 +269,14 @@ export default class IABookActions extends LitElement {
282
269
  * dispatched this event from bookreader page changed
283
270
  */
284
271
  window.addEventListener('BookReader:userAction', () => {
272
+ log('IABookActions:BookReader:userAction');
273
+ if (this.borrowType === 'browsed') {
274
+ this.autoLoanRenewChecker(true);
275
+ }
276
+ });
277
+
278
+ window.focus(() => {
279
+ log('WINDOW FOCUS', this.borrowType);
285
280
  if (this.borrowType === 'browsed') {
286
281
  this.autoLoanRenewChecker(true);
287
282
  }
@@ -306,27 +301,23 @@ export default class IABookActions extends LitElement {
306
301
  this.loanRenewResult = this.loanRenewHelper.result;
307
302
  }
308
303
 
309
- /**
310
- * create / select the modal-manager component on DOM
311
- * @memberof IABookActions
312
- */
313
- async useModalManager() {
314
- if (!this.modal) {
315
- this.modal = document.querySelector('modal-manager');
316
-
317
- if (!this.modal) this.modal = document.createElement('modal-manager');
318
- }
304
+ /** @returns HTMLElement */
305
+ get modal() {
306
+ const modalOnDom = document.querySelector('modal-manager');
319
307
 
320
- this.modal.id = 'action-bar-modal';
321
- await document.body.appendChild(this.modal);
308
+ modalOnDom.id = 'action-bar-modal';
309
+ return modalOnDom;
322
310
  }
323
311
 
324
312
  /**
325
313
  * Show the waring modal to ask user if they are still reading
326
314
  */
327
315
  async showWarningModal() {
328
- console.log('****** showWarningModal ******');
329
- await this.useModalManager();
316
+ log('****** showWarningModal ******');
317
+ // clear modal
318
+ this.modal.customModalContent = nothing;
319
+ this.modal?.closeModal();
320
+ this.loanRenewResult = { texts: '', renewNow: false };
330
321
 
331
322
  // if secondsLeft < 60, consider it 1 minute
332
323
  let { secondsLeft } = this.loanRenewResult;
@@ -336,76 +327,130 @@ export default class IABookActions extends LitElement {
336
327
  secondsLeft = secondsLeft > 60 ? secondsLeft : 60;
337
328
  }
338
329
 
339
- const config = new ModalConfig();
340
- config.headline = 'Are you still here?';
341
- config.showCloseButton = false;
342
- config.closeOnBackdropClick = false;
343
- config.message = this.loanRenewHelper?.getMessageTexts(
344
- this.loanRenewResult.texts,
345
- secondsLeft
346
- );
330
+ const config = new ModalConfig({
331
+ headline: 'Are you still reading?',
332
+ headerColor: '#194880',
333
+ showCloseButton: false,
334
+ closeOnBackdropClick: false,
335
+ message: this.loanRenewHelper?.getMessageTexts(
336
+ this.loanRenewResult.texts,
337
+ secondsLeft
338
+ ),
339
+ });
347
340
 
348
- const customContent = html`<br />
349
- <div id="custom-buttons" style="display:flex;justify-content:center;">
341
+ const customModalContent = html`<br />
342
+ <div
343
+ id="book-action-bar-custom-buttons"
344
+ style="display:flex;justify-content:center;"
345
+ >
350
346
  <button
351
347
  style="${modalButtonStyle.iaButton} ${modalButtonStyle.renew}"
352
- @click=${async event => {
353
- await this.changeModalState(event);
354
- this.loanRenewResult = { texts: '', renewNow: true };
355
- }}
348
+ @click=${() => this.patronWantsToRenewBook()}
356
349
  >
357
350
  Keep reading
358
351
  </button>
359
352
  <button
360
353
  style="${modalButtonStyle.iaButton} ${modalButtonStyle.return}"
361
- @click=${event => {
362
- this.changeModalState(event);
363
- document.querySelector('ia-book-actions').disableActionGroup = true;
364
- this.returnNow = true;
365
- }}
354
+ @click=${() => this.patronWantsToReturnBook()}
366
355
  >
367
356
  Return the book
368
357
  </button>
369
358
  </div> `;
370
359
 
371
- this.modal?.showModal({ config, customModalContent: customContent });
360
+ this.modal.setAttribute('aria-live', 'assertive');
361
+ await this.modal?.showModal({ config, customModalContent });
372
362
  }
373
363
 
374
- /**
375
- * helper function to change button click states on modal-manager
376
- * - add loader on button click
377
- * - change button appearance
378
- *
379
- * @param {Event} event
380
- */
381
- changeModalState(event) {
382
- if (event.target === undefined) return;
364
+ /** @param { 'renewBook' | 'returnBook' } buttonToDisable */
365
+ async showWarningDisabledModal(buttonToDisable = 'renewBook') {
366
+ // if secondsLeft < 60, consider it 1 minute
367
+ let { secondsLeft } = this.loanRenewResult;
368
+ if (secondsLeft === undefined) {
369
+ secondsLeft = this.lendingStatus.secondsLeftOnLoan;
370
+ } else {
371
+ secondsLeft = secondsLeft > 60 ? secondsLeft : 60;
372
+ }
383
373
 
384
- const button = event.target;
385
- button.innerHTML = `<ia-activity-indicator
386
- mode="processing"
387
- style=${modalButtonStyle.loaderIcon}
388
- ></ia-activity-indicator>`;
374
+ const config = new ModalConfig({
375
+ headline: 'Are you still reading?',
376
+ headerColor: '#194880',
377
+ showCloseButton: false,
378
+ closeOnBackdropClick: false,
379
+ message: this.loanRenewHelper?.getMessageTexts(
380
+ this.loanRenewResult.texts,
381
+ secondsLeft
382
+ ),
383
+ });
389
384
 
390
- const parentElement = event.target.parentNode;
391
- parentElement.style.pointerEvents = 'none';
392
- parentElement.style.opacity = 0.8;
385
+ const customModalContent = html`<br />
386
+ <div
387
+ id="disabled-book-action-bar-custom-buttons"
388
+ style="display:flex;justify-content:center; opacity:0.8; pointer-events:none;"
389
+ >
390
+ <button
391
+ disabled
392
+ style="${modalButtonStyle.iaButton} ${modalButtonStyle.renew}"
393
+ >
394
+ ${buttonToDisable === 'renewBook'
395
+ ? html`<ia-activity-indicator
396
+ mode="processing"
397
+ style=${modalButtonStyle.loaderIcon}
398
+ ></ia-activity-indicator>`
399
+ : 'Keep reading'}
400
+ </button>
401
+ <span
402
+ style="position: absolute; visibility: none; height: 1px; width: 1px; overflow: hidden;"
403
+ >Renewing loan, one moment please.</span
404
+ >
405
+ <button
406
+ disabled
407
+ style="${modalButtonStyle.iaButton} ${modalButtonStyle.return}"
408
+ >
409
+ ${buttonToDisable === 'returnBook'
410
+ ? html`<ia-activity-indicator
411
+ mode="processing"
412
+ style=${modalButtonStyle.loaderIcon}
413
+ ></ia-activity-indicator>`
414
+ : 'Return the book'}
415
+ </button>
416
+ </div> `;
417
+
418
+ await this.modal?.showModal({ config, customModalContent });
419
+ }
420
+
421
+ /** Handles Renew action in warning modal */
422
+ async patronWantsToRenewBook() {
423
+ this.showWarningDisabledModal();
424
+ this.loanRenewResult = { texts: '', renewNow: true };
425
+ }
426
+
427
+ async patronWantsToReturnBook() {
428
+ this.showWarningDisabledModal('returnBook');
429
+ document.querySelector('ia-book-actions').disableActionGroup = true;
430
+ this.returnNow = true;
431
+
432
+ this.lendingStatus = {
433
+ ...this.lendingStatus,
434
+ browsingExpired: true,
435
+ secondsLeftOnLoan: 0,
436
+ };
393
437
  }
394
438
 
395
439
  /**
396
440
  * Show modal when book is auto returned
397
441
  */
398
442
  async showExpiredModal() {
399
- console.log('showExpiredModal()');
400
- await this.useModalManager();
401
-
402
- const config = new ModalConfig();
403
- config.headline = '';
404
- config.showCloseButton = false;
405
- config.closeOnBackdropClick = false;
406
- config.message = 'This book has been returned due to inactivity.';
443
+ log('showExpiredModal()');
444
+
445
+ const config = new ModalConfig({
446
+ headline: '',
447
+ showCloseButton: false,
448
+ closeOnBackdropClick: false,
449
+ headerColor: '#194880',
450
+ message: 'This book has been returned due to inactivity.',
451
+ });
407
452
 
408
- const customContent = html`<br />
453
+ const customModalContent = html`<br />
409
454
  <div style="text-align: center">
410
455
  <button
411
456
  style="${modalButtonStyle.iaButton} ${modalButtonStyle.renew}"
@@ -417,15 +462,7 @@ export default class IABookActions extends LitElement {
417
462
  </button>
418
463
  </div> `;
419
464
 
420
- this.modal?.showModal({ config, customModalContent: customContent });
421
- }
422
-
423
- /**
424
- * close/hide modal-manager
425
- */
426
- async closeModalManager() {
427
- this.modal = document.querySelector('modal-manager');
428
- this.modal?.closeModal();
465
+ await this.modal?.showModal({ config, customModalContent });
429
466
  }
430
467
 
431
468
  /**
@@ -489,7 +526,11 @@ export default class IABookActions extends LitElement {
489
526
  async browseHasExpired() {
490
527
  window?.IALendingIntervals?.clearAll();
491
528
 
492
- const currStatus = { ...this.lendingStatus, browsingExpired: true };
529
+ const currStatus = {
530
+ ...this.lendingStatus,
531
+ browsingExpired: true,
532
+ secondsLeftOnLoan: 0,
533
+ };
493
534
  this.lendingStatus = currStatus;
494
535
 
495
536
  // remove respected key:value for loan-renew
@@ -507,54 +548,38 @@ export default class IABookActions extends LitElement {
507
548
  }
508
549
 
509
550
  async startBrowseTimer() {
510
- clearTimeout(this.browseTimer);
511
- console.log('startBrowseTimerstartBrowseTimer just cleared timer', this.browseTimer, this.lendingStatus);
551
+ window.IALendingIntervals.clearBrowseExpireTimeout();
512
552
 
513
553
  const {
514
554
  browsingExpired,
515
555
  user_has_browsed,
516
556
  secondsLeftOnLoan,
517
557
  } = this.lendingStatus;
558
+
559
+ log('startBrowseTimerstartBrowseTimer just cleared timer', {
560
+ browsingExpired,
561
+ user_has_browsed,
562
+ secondsLeftOnLoan,
563
+ });
564
+
518
565
  if (!user_has_browsed || browsingExpired) {
519
- console.log('user not browsed', this.lendingStatus);
566
+ log('user not browsed', {
567
+ user_has_browsed,
568
+ browsingExpired,
569
+ secondsLeftOnLoan,
570
+ });
520
571
  return;
521
572
  }
522
573
 
523
- this.browseTimer = setTimeout(() => {
524
- console.log('start `this.browseTimer will fire his.browseHasExpired() - secondsLeftOnLoan', secondsLeftOnLoan);
574
+ window.IALendingIntervals.browseExpireTimeout = setTimeout(() => {
575
+ log(
576
+ 'start `this.browseTimer will fire his.browseHasExpired() - secondsLeftOnLoan',
577
+ secondsLeftOnLoan
578
+ );
525
579
  this.browseHasExpired();
526
580
  }, secondsLeftOnLoan * 1000);
527
581
  }
528
582
 
529
- /**
530
- * Reset timer animation state after book renewed
531
- */
532
- async resetTimerCountState() {
533
- const timerCountdown = this._shadowRoot.querySelector('timer-countdown');
534
- if (!timerCountdown) return;
535
-
536
- const secondsLeft = Number.parseInt((Number(this.lendingStatus.secondsLeftOnLoan)), 10);
537
- const strokeDashOffset = Number.parseInt(((secondsLeft / this.loanRenewTimeConfig.loanTotalTime) * 315) * 1.5, 10);
538
-
539
-
540
- console.log('resetTimerCountState === ', { secondsLeft, strokeDashOffset });
541
-
542
- // set seconds left in loan expire
543
- timerCountdown.style?.setProperty('--secondsLeftOnLoan', `${secondsLeft}s`);
544
-
545
- // set circle stroke offset left in loan expire
546
- timerCountdown.style?.setProperty(
547
- '--strokeLeftOnLoan',
548
- `${strokeDashOffset}` // the perimeter of the circle = (π * 2 * radius)
549
- );
550
-
551
- const animationCircle = timerCountdown.shadowRoot.querySelector('.circle');
552
- animationCircle.style.animationName = 'none';
553
- setTimeout(() => {
554
- animationCircle.style.animationName = 'circletimer';
555
- }, 100);
556
- }
557
-
558
583
  render() {
559
584
  if (this.barType === 'title') {
560
585
  return html`<section class="lending-wrapper">
@@ -574,6 +599,10 @@ export default class IABookActions extends LitElement {
574
599
  ></book-title-bar>`;
575
600
  }
576
601
 
602
+ get timerCountdownEl() {
603
+ return this.shadowRoot.querySelector('timer-countdown');
604
+ }
605
+
577
606
  get bookActionBar() {
578
607
  return html`
579
608
  <collapsible-action-group
@@ -599,8 +628,9 @@ export default class IABookActions extends LitElement {
599
628
  </collapsible-action-group>
600
629
  ${this.textGroupTemplate} ${this.infoIconTemplate}
601
630
  <timer-countdown
602
- .secondsLeftOnLoan=${Number(this.lendingStatus.secondsLeftOnLoan)}
603
- class=${this.borrowType === 'browsed' ? '' : 'hide'}
631
+ .secondsLeftOnLoan=${Math.round(
632
+ Number(this.lendingStatus.secondsLeftOnLoan)
633
+ )}
604
634
  ></timer-countdown>
605
635
  `;
606
636
  }
@@ -616,18 +646,17 @@ export default class IABookActions extends LitElement {
616
646
  if (this.loanRenewResult.renewNow) {
617
647
  window?.IALendingIntervals?.clearAll();
618
648
 
649
+ // Now, let's reset loan duration & this.lendingStatus
650
+ const loanTime = await this.localCache.get(`${this.identifier}-loanTime`);
651
+ const secondsLeft = Math.round((loanTime - new Date()) / 1000); // different in seconds
652
+
619
653
  // testing console....
620
- console.log('IABookActions: AutoRenewed:- ', {
654
+ log('~~~ IABookActions: handleLoanAutoRenewed:- ', {
621
655
  ajaxResponse: event?.detail?.data,
622
656
  loanRenewResult: this.loanRenewResult,
623
- secondsLeftOnLoan: Math.round(this.lendingStatus.secondsLeftOnLoan), // stale? how to fix
657
+ secondsLeftOnLoan: secondsLeft,
624
658
  });
625
659
 
626
-
627
- // Now, let's reset loan duration & this.lendingStatus
628
- const loanTime = await this.localCache.get(`${this.identifier}-loanTime`);
629
- const secondsLeft = (loanTime - new Date()) / 1000; // different in seconds
630
-
631
660
  const currStatus = {
632
661
  ...this.lendingStatus,
633
662
  user_has_browsed: true,
@@ -636,19 +665,10 @@ export default class IABookActions extends LitElement {
636
665
  };
637
666
  this.lendingStatus = currStatus;
638
667
 
639
- console.log('~~~ handleLoanAutoRenewed - new this.lendingStatus', {
640
- secondsLeftOnLoan: this.lendingStatus.secondsLeftOnLoan
641
- });
642
-
643
- /**
644
- * reset timer countdown states
645
- */
646
- await this.resetTimerCountState();
647
-
648
668
  // close the modal
649
- this.modal?.remove();
650
- this.modal?.showModal({ config: {}, customModalContent: `` });
651
-
669
+ this.modal?.closeModal();
670
+ this.modal.removeAttribute('id');
671
+ this.modal.customModalContent = nothing;
652
672
  window?.Sentry?.captureMessage(sentryLogs.bookHasRenewed);
653
673
  }
654
674
  }
@@ -659,7 +679,6 @@ export default class IABookActions extends LitElement {
659
679
  * @memberof IABookActions
660
680
  */
661
681
  startTimerCountdown() {
662
- console.log('startTimerCountdown()');
663
682
  window?.IALendingIntervals?.clearTimerCountdown();
664
683
 
665
684
  let secondsLeft = Number(this.lendingStatus.secondsLeftOnLoan);
@@ -674,14 +693,18 @@ export default class IABookActions extends LitElement {
674
693
  // if really updated, escape from here
675
694
  const resync = await this.reSyncTimerIfGoneOff(secondsLeft);
676
695
 
677
- console.log("*** startTimerCountdown = reSyncTimerIfGoneOff ***", { secondsLeft, resync });
678
-
679
696
  if (resync) {
680
- console.log('have resyncd, will end startTimerCountdown here - ');
697
+ log('timer has resyncd, abort stale startTimerCountdown - ', {
698
+ secondsLeft,
699
+ });
681
700
  return;
682
701
  }
683
702
 
684
- console.log('startTimerCountdown - NO RESYNC');
703
+ log('startTimerCountdown - countdown still valid. continue...', {
704
+ resync,
705
+ secondsLeft,
706
+ loanRenewAtLast: this.loanRenewTimeConfig.loanRenewAtLast,
707
+ });
685
708
 
686
709
  /**
687
710
  * execute from last 10th minute to 0th minute
@@ -709,15 +732,6 @@ export default class IABookActions extends LitElement {
709
732
  * @memberof TimerCountdown
710
733
  */
711
734
  async reSyncTimerIfGoneOff(timerSecondsLeft) {
712
- // debug info
713
- console.log(
714
- new Date().toLocaleTimeString(),
715
- 'timeLeftInSec',
716
- timerSecondsLeft,
717
- ' ||| timeLeftInMin',
718
- Math.ceil(timerSecondsLeft / 60)
719
- );
720
-
721
735
  const currentTime = new Date();
722
736
 
723
737
  // current time - loan time
@@ -730,19 +744,27 @@ export default class IABookActions extends LitElement {
730
744
  // convert in minutes
731
745
  const whatIsleft = Math.round(timerSecondsLeft);
732
746
  const whatShouldLeft = Math.round(secondsShouldLeft);
747
+ const timerElSeconds = this.timerCountdownEl.secondsLeftOnLoan || 0;
733
748
 
734
- if (whatIsleft !== whatShouldLeft) {
735
- // debug info
736
- console.log(
737
- `timer is gone off, let's re-sync, whatIsleft -> ${whatIsleft}, whatShouldLeft -> ${whatShouldLeft}`
738
- );
749
+ log(`reSyncTimerIfGoneOff?`, {
750
+ whatIsleft,
751
+ whatShouldLeft,
752
+ timerElSeconds,
753
+ timeLeftInMin: Math.ceil(timerSecondsLeft / 60),
754
+ });
739
755
 
740
- // the seconds should left in timer again
756
+ if (timerElSeconds !== whatShouldLeft || whatIsleft !== whatShouldLeft) {
757
+ // set lending status with new time to update decrementor
741
758
  const currStatus = {
742
759
  ...this.lendingStatus,
743
- secondsLeftOnLoan: secondsShouldLeft,
760
+ secondsLeftOnLoan: whatShouldLeft,
744
761
  };
745
762
  this.lendingStatus = currStatus;
763
+ }
764
+
765
+ if (whatIsleft !== whatShouldLeft) {
766
+ log(`timer is gone off, let's re-sync,`);
767
+
746
768
  return true;
747
769
  }
748
770
  return false;
@@ -757,7 +779,7 @@ export default class IABookActions extends LitElement {
757
779
  * @memberof IABookActions
758
780
  */
759
781
  async loanRenewAttempt(secondsLeft) {
760
- console.log('loanRenewAttempt()', secondsLeft, this.loanRenewResult);
782
+ log('loanRenewAttempt()', secondsLeft, this.loanRenewResult);
761
783
  let loanSecondsLeft = secondsLeft;
762
784
  /**
763
785
  * auto-renew is not possible in last seconds (let say 50 second) because,
@@ -767,7 +789,7 @@ export default class IABookActions extends LitElement {
767
789
  * so if seconds left is < 50, just expire the loan
768
790
  */
769
791
  if (loanSecondsLeft < 50) {
770
- console.log('if (loanSecondsLeft < 50) {');
792
+ log('if (loanSecondsLeft < 50) {');
771
793
  await this.browseHasExpired();
772
794
  return;
773
795
  }
@@ -882,8 +904,6 @@ export default class IABookActions extends LitElement {
882
904
 
883
905
  /* show error message if something went wrong */
884
906
  async showErrorModal(errorMsg, action) {
885
- await this.useModalManager();
886
-
887
907
  const modalConfig = new ModalConfig({
888
908
  title: 'Lending error',
889
909
  message: errorMsg,
@@ -910,7 +930,7 @@ export default class IABookActions extends LitElement {
910
930
  <code>errorLog: ${errorMsg}</code>`;
911
931
  }
912
932
 
913
- this.modal?.showModal({
933
+ await this.modal?.showModal({
914
934
  config: modalConfig,
915
935
  });
916
936
  }
@@ -189,7 +189,7 @@ describe('Borrow status actions', () => {
189
189
  ];
190
190
 
191
191
  expect(el.primaryTitle).to.equal(
192
- 'Renews automatically as long as pages are turned.'
192
+ 'Renews automatically with continued use.'
193
193
  );
194
194
  expect(el.primaryActions.length).to.equal(2);
195
195
  expect(el.primaryActions[0].text).to.equal('Log In and Borrow');