@internetarchive/ia-book-actions 0.1.9 → 0.1.11-a1

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
@@ -88,6 +88,7 @@
88
88
 
89
89
  <body>
90
90
  <ia-book-actions></ia-book-actions>
91
+ <modal-manager></modal-manager>
91
92
 
92
93
  <script type="module">
93
94
  /* eslint-disable */
@@ -97,6 +98,7 @@
97
98
  defaultLendingStatus,
98
99
  canBrowseAndBorrow,
99
100
  } from '../src/assets/data/lending-get-status-service.js';
101
+ import '@internetarchive/modal-manager';
100
102
  import { ModalConfig } from '@internetarchive/modal-manager';
101
103
  import { LocalCache } from '@internetarchive/local-cache';
102
104
 
@@ -290,7 +292,7 @@
290
292
 
291
293
  document.querySelector('.pageChangedEvent').addEventListener('click', () => {
292
294
  // dispatch event to say book page has flipped
293
- document.querySelector('ia-book-actions').dispatchEvent(
295
+ window.dispatchEvent(
294
296
  new CustomEvent('BookReader:userAction', {
295
297
  detail: {},
296
298
  bubbles: true,
@@ -0,0 +1,146 @@
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: 50px;
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
+ body.modal-manager-open {
76
+ overflow: hidden;
77
+ }
78
+
79
+ modal-manager[mode='closed'] {
80
+ display: none;
81
+ }
82
+ </style>
83
+ </head>
84
+
85
+ <body>
86
+ <ia-bookreader class="focus-on-child-only" signedin="" fullscreen="">
87
+ <div slot="header">
88
+ <ia-book-actions></ia-book-actions>
89
+ </div>
90
+ <div slot="main">
91
+ <div id="BookReader"></div>
92
+ </div>
93
+ </ia-bookreader>
94
+
95
+
96
+ <script type="module">
97
+ /* eslint-disable */
98
+ import '@internetarchive/bookreader';
99
+ import '@internetarchive/bookreader/BookReader/jquery-1.10.1.js';
100
+ import '@internetarchive/bookreader/BookReader/BookReader.js';
101
+ import '../src/ia-book-actions.js';
102
+ import {
103
+ defaultLendingStatus,
104
+ canBrowseAndBorrow,
105
+ } from '../src/assets/data/lending-get-status-service.js';
106
+ import { ModalConfig } from '@internetarchive/modal-manager';
107
+
108
+ const userid = '@neeraj-archive';
109
+ const identifier = 'naturalhistoryof00unse_4111';
110
+ let bwbPurchaseUrl = 'https://www.betterworldbooks.com/product/detail/cambridge-ancient-hist-v04-0521044863';
111
+
112
+ let iaBookActions = document.querySelector('ia-book-actions');
113
+
114
+ // for debugging
115
+ window.iaBookActions = iaBookActions;
116
+
117
+ const modalConfig = new ModalConfig();
118
+ modalConfig.headerColor = '#d9534f';
119
+
120
+ let lendingBarPostInit = function lendingBarPostInit() {
121
+ setTimeout(function() {
122
+ console.log('executed', 'br.init()');
123
+ }, 100);
124
+ }
125
+
126
+ // set defaultLendingStatus for unavailable (without borrowables) book
127
+ iaBookActions.userid = userid;
128
+ iaBookActions.identifier = identifier;
129
+ iaBookActions.bookTitle = 'Contemporary Black biography. Volume 39 : profiles from the interContemporary Black biography. Volume 39';
130
+ iaBookActions.lendingStatus = canBrowseAndBorrow;
131
+ iaBookActions.bwbPurchaseUrl = '';
132
+ iaBookActions.modalConfig = modalConfig;
133
+ iaBookActions.lendingBarPostInit = lendingBarPostInit;
134
+ iaBookActions.tokenDelay = 2000; // 2 seconds
135
+ iaBookActions.returnUrl = 'https://openlibrary.org';
136
+
137
+ let lendingStatus1 = iaBookActions.lendingStatus;
138
+
139
+ const manifest = await fetch('https://ia801302.us.archive.org/BookReader/BookReaderJSIA.php?id=goody&itemPath=/29/items/goody&server=ia801302.us.archive.org&format=jsonp&subPrefix=goody&requestUri=/details/goody/mode/1up%3Fview%3Dtheater%26ui%3Dembed%26wrapper%3Dfalse');
140
+
141
+ console.log('manifest ****', manifest).then(res => res.json());
142
+ </script>
143
+
144
+ </div>
145
+ </body>
146
+ </html>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@internetarchive/ia-book-actions",
3
- "version": "0.1.9",
3
+ "version": "0.1.11-a1",
4
4
  "description": "Bookreader actions for borrowable book",
5
5
  "author": "Internet Archive",
6
6
  "license": "AGPL-3.0-only",
@@ -18,9 +18,10 @@
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
- "@internetarchive/modal-manager": "^0.2.8",
24
+ "@internetarchive/modal-manager": "^0.2.12",
24
25
  "@internetarchive/shared-resize-observer": "^0.2.0",
25
26
  "@internetarchive/toast-manager": "0.1.0",
26
27
  "lit": "^2.5.0"
@@ -100,7 +100,7 @@ export const canBrowseAndBorrow = {
100
100
  // userid: "",
101
101
  bookUrl: '/details/practicalorganic00plim',
102
102
  daysLeftOnLoan: 0,
103
- isAdmin: true,
103
+ isAdmin: false,
104
104
  isArchiveOrgLending: true,
105
105
  isAvailable: false,
106
106
  isAvailableForBrowsing: false,
@@ -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
  /**
File without changes
@@ -41,15 +41,18 @@ 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
- disconnectedCallback() {
48
- window?.IALendingIntervals?.clearTimerCountdown();
47
+ connectedCallback() {
48
+ super.connectedCallback();
49
+ console.log('connectedCallback - timerCountdown');
49
50
  }
50
51
 
51
- firstUpdated() {
52
- this.timerCountdown();
52
+ disconnectedCallback() {
53
+ console.log('disconnectedCallback - timerCountdown IN', window.IALendingIntervals);
54
+ window?.IALendingIntervals?.clearTimerCountdown();
55
+ console.log('disconnectedCallback - timerCountdown cleared', window.IALendingIntervals);
53
56
  }
54
57
 
55
58
  updated(changed) {
@@ -58,41 +61,52 @@ export default class TimerCountdown extends LitElement {
58
61
  }
59
62
  }
60
63
 
64
+ async timerInterval() {
65
+ let secondsLeft = this.secondsLeftOnLoan;
66
+
67
+ secondsLeft -= this.timerExecutionSeconds;
68
+ secondsLeft = Math.round(secondsLeft); // round number
69
+
70
+ // re-sync timer if gone off because of background window
71
+
72
+ // do we need a window.focus anywhere?
73
+ await this.reSyncTimerIfGoneOff(secondsLeft);
74
+
75
+ /**
76
+ * execute from last 10th minute to 0th minute // arbitrary?
77
+ * - 10th - to check if user has viewed
78
+ * - till 0th - to show warning msg with remaining time to auto expired
79
+ * @see IABookActions::bindLoanRenewEvents
80
+ */
81
+ if (secondsLeft <= this.loanRenewAtLast) {
82
+ this.dispatchLoanRenewEvent(secondsLeft);
83
+ }
84
+
85
+ // clear interval in secondsLeft if less
86
+ if (secondsLeft <= this.timerExecutionSeconds) {
87
+ this.disconnectedCallback();
88
+ window?.Sentry?.captureMessage(sentryLogs.clearOneHourTimer);
89
+ }
90
+ }
91
+
61
92
  timerCountdown() {
93
+ console.log('TIMER COUNTDOWN', window.IALendingIntervals, this.timerExecutionSeconds );
62
94
  this.disconnectedCallback();
63
95
 
64
- let secondsLeft = this.secondsLeftOnLoan;
96
+
65
97
 
66
98
  // store current time
67
99
  this.timeWhenTimerStart = new Date();
68
100
 
101
+ // this.dispatchLoanRenewEvent(secondsLeft);
69
102
  /**
70
103
  * set interval in window object
71
104
  * @see ia-lending-intervals.js
72
105
  */
73
- window.IALendingIntervals.timerCountdown = setInterval(async () => {
74
- secondsLeft -= this.timerExecutionSeconds;
75
- secondsLeft = Math.round(secondsLeft); // round number
76
-
77
- // re-sync timer if gone off because of background window
78
- await this.reSyncTimerIfGoneOff(secondsLeft);
79
-
80
- /**
81
- * execute from last 10th minute to 0th minute
82
- * - 10th - to check if user has viewed
83
- * - till 0th - to show warning msg with remaining time to auto expired
84
- * @see IABookActions::bindLoanRenewEvents
85
- */
86
- if (secondsLeft <= this.loanRenewAtLast) {
87
- this.dispatchLoanRenewEvent(secondsLeft);
88
- }
89
-
90
- // clear interval in secondsLeft if less
91
- if (secondsLeft <= this.timerExecutionSeconds) {
92
- this.disconnectedCallback();
93
- window?.Sentry?.captureMessage(sentryLogs.clearOneHourTimer);
94
- }
95
- }, this.timerExecutionSeconds * 1000);
106
+ window.IALendingIntervals.timerCountdown = setInterval(
107
+ () => this.timerInterval(),
108
+ this.timerExecutionSeconds * 1000
109
+ );
96
110
  }
97
111
 
98
112
  /**
@@ -150,6 +164,9 @@ export default class TimerCountdown extends LitElement {
150
164
  );
151
165
  this.secondsLeftOnLoan = secondsShouldLeft; // the seconds should left in timer again
152
166
  }
167
+
168
+ // when do we resync timer?
169
+ // return true or false?
153
170
  }
154
171
 
155
172
  /**
@@ -171,6 +188,7 @@ export default class TimerCountdown extends LitElement {
171
188
  }
172
189
 
173
190
  render() {
191
+ console.log("TIMER COUNTDOWN RENDER");
174
192
  return html`
175
193
  <svg
176
194
  width="14"
@@ -178,7 +196,7 @@ export default class TimerCountdown extends LitElement {
178
196
  viewBox="0 0 100 100"
179
197
  xmlns="http://www.w3.org/2000/svg"
180
198
  >
181
- <circle class="circle" cx="50" cy="50" r="50" />
199
+ <circle id="timer-countdown" class="circle" cx="50" cy="50" r="50" />
182
200
  </svg>
183
201
  <span class="sr-only">${this.remainingTime} left</span>
184
202
  `;
@@ -41,7 +41,13 @@ window.IALendingIntervals = {
41
41
  * clear all intervals being used acros lending system
42
42
  */
43
43
  clearAll: () => {
44
- window.clearInterval(window?.IALendingIntervals?.tokenPoller);
45
- window.clearInterval(window?.IALendingIntervals?.timerCountdown);
44
+ try {
45
+
46
+ window.clearInterval(window?.IALendingIntervals.tokenPoller);
47
+ window.clearInterval(window?.IALendingIntervals.timerCountdown);
48
+ } catch(e) {
49
+ console.error(e);
50
+ }
51
+
46
52
  },
47
53
  };
@@ -164,7 +164,7 @@ export default class ActionsHandler extends LitElement {
164
164
  /**
165
165
  * excute function when loan is returning
166
166
  *
167
- * @param {string} type - loan return type returnNow|''
167
+ * @param {'returnNow' | ''} type
168
168
  */
169
169
  handleReturnIt(type = '') {
170
170
  const action = 'return_loan';
@@ -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 each hour as long as pages are turned.',
43
+ available_1hr: 'Renews automatically as long as pages are turned.',
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: '',
52
+ session_expired: 'Renews automatically as long as pages are turned.',
53
53
  unavailable: 'This book is not available at this time.',
54
54
  };
55
55
 
@@ -98,8 +98,7 @@ export class GetLendingActions {
98
98
  let isBrowsing =
99
99
  lendingStatus.user_has_browsed && !lendingStatus.browsingExpired;
100
100
  if (isBrowsing) {
101
- primaryTitleText =
102
- 'Returns automatically after 25 minutes of inactivity.';
101
+ primaryTitleText = bookTitles.available_1hr;
103
102
  } else {
104
103
  primaryTitleText = `Your loan of this book has ${lendingStatus.daysLeftOnLoan} days left.`;
105
104
  }
@@ -144,14 +144,14 @@ export default class LoanAnanlytics {
144
144
  * @memberof LoanAnanlytics
145
145
  */
146
146
  sendEvent(eventCategory, eventAction, label, extraParams) {
147
- console.log(
148
- 'eventCategory:-',
149
- eventCategory,
150
- '\t||\teventAction:-',
151
- eventAction,
152
- '\t||\tlabel:-',
153
- label
154
- );
147
+ // console.log(
148
+ // 'eventCategory:-',
149
+ // eventCategory,
150
+ // '\t||\teventAction:-',
151
+ // eventAction,
152
+ // '\t||\tlabel:-',
153
+ // label
154
+ // );
155
155
 
156
156
  window?.archive_analytics?.send_event_no_sampling(
157
157
  eventCategory,
@@ -16,7 +16,7 @@ export class LoanRenewHelper {
16
16
  this.loanRenewMessage =
17
17
  'This book has been renewed for #time #unitsOfTime.';
18
18
  this.loanReturnWarning =
19
- 'This book will be automatically returned in #time #unitsOfTime unless you turn a page.';
19
+ 'With no action, this book will be auto-returned in #time #unitsOfTime.';
20
20
 
21
21
  // private props
22
22
  this.result = {
@@ -26,11 +26,14 @@ export class LoanRenewHelper {
26
26
  }
27
27
 
28
28
  handleLoanRenew() {
29
+ console.log('handleLoanRenew', this.hasPageChanged);
29
30
  try {
30
31
  if (this.hasPageChanged) {
31
- return this.pageChanged(); // user clicked on page
32
+ this.pageChanged(); // user clicked on page
33
+ return;
32
34
  }
33
- return this.autoChecker(); // auto checker at last 10th minute
35
+ this.autoChecker(); // auto checker at last 10th minute
36
+ return;
34
37
  } catch (error) {
35
38
  console.log(error);
36
39
  }
@@ -39,7 +42,7 @@ export class LoanRenewHelper {
39
42
  }
40
43
 
41
44
  /**
42
- * Trigger this function when user has browsed a book and change the post
45
+ * Trigger this function when user has browsed a book and change the page
43
46
  * - every time user change the page, we set current time in indexedDB
44
47
  * - also check if need to auto renew current loan
45
48
  *
@@ -54,13 +57,16 @@ export class LoanRenewHelper {
54
57
 
55
58
  const lastTimeFrame = this.changeTime(loanTime, loanRenewAtLast, 'sub');
56
59
 
60
+ console.log("PAGE CHANGED ~~~~~~~~~ lastTimeFram, loanTime", { lastTimeFrame, loanTime });
61
+
57
62
  // if user viewed new page in last 10 minutes, renew immediately
58
63
  if (lastTimeFrame !== null && currentTime >= lastTimeFrame) {
59
64
  this.result.texts = this.loanRenewMessage;
60
65
  this.result.renewNow = true;
61
66
  }
62
-
63
- this.setPageChangedTime();
67
+ console.log("PAGE CHANGED ~~~~~~~~~ ", this.result);
68
+ debugger;
69
+ await this.setPageChangedTime();
64
70
  return this.result;
65
71
  }
66
72
 
@@ -71,6 +77,7 @@ export class LoanRenewHelper {
71
77
  * @returns {this.result}
72
78
  */
73
79
  async autoChecker() {
80
+ console.log('autoChecker ***!!!!!');
74
81
  const { pageChangedInLast } = this.loanRenewTimeConfig;
75
82
  const pageChangedTime = await this.localCache.get(
76
83
  `${this.identifier}-pageChangedTime`
@@ -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,12 +24,28 @@ 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
+ */
31
+ export const modalButtonStyle = {
32
+ iaButton:
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;',
38
+ refresh:
39
+ 'background:none;font-size:inherit;border:0;padding:0;color:#0000ee;cursor:pointer;text-decoration:underline',
40
+ };
41
+
26
42
  export default class IABookActions extends LitElement {
27
43
  static get properties() {
28
44
  return {
29
45
  userid: { type: String },
30
46
  identifier: { type: String },
31
47
  bookTitle: { type: String },
48
+ /** */
32
49
  lendingStatus: { type: Object },
33
50
  returnUrl: { type: String },
34
51
  width: { type: Number },
@@ -61,7 +78,7 @@ export default class IABookActions extends LitElement {
61
78
  this.barType = 'action'; // 'title'|'action'
62
79
  this.sharedObserver = undefined;
63
80
  this.disableActionGroup = false;
64
- this.modal = undefined;
81
+ // this.modal = undefined;
65
82
  this.tokenDelay = 120; // in seconds
66
83
 
67
84
  // private props
@@ -71,10 +88,19 @@ export default class IABookActions extends LitElement {
71
88
  this.secondaryActions = [];
72
89
  this.lendingOptions = {};
73
90
  this.borrowType = ''; // (browsed|borrowed)
74
- this.consecutiveLoanCounts = 1; // consecutive loan count
91
+ this.browseTimer = undefined;
92
+
93
+ /** @deprecated */
94
+ this.toast = undefined;
95
+ /** @deprecated */
75
96
  this.suppressToast = false;
97
+ /** @deprecated */
76
98
  this.suppressAutoRenew = false;
77
- this.browseTimer = undefined;
99
+
100
+ /**
101
+ * when user click on [return the book] button on warning modal
102
+ */
103
+ this.returnNow = false;
78
104
 
79
105
  /**
80
106
  * contains one hour auto-loan-renew time configuration
@@ -103,6 +129,11 @@ export default class IABookActions extends LitElement {
103
129
  this._shadowRoot = this.attachShadow({ mode: 'open' });
104
130
  }
105
131
 
132
+ connectedCallback() {
133
+ super.connectedCallback();
134
+ console.log('connectedCallback - ia-book-actions');
135
+ }
136
+
106
137
  disconnectedCallback() {
107
138
  // clear all intervals for lending system
108
139
  window?.IALendingIntervals?.clearAll();
@@ -112,21 +143,28 @@ export default class IABookActions extends LitElement {
112
143
  }
113
144
 
114
145
  firstUpdated() {
146
+ // window.focus => reset timer & also1w3w
147
+ window.addEventListener('focus', () => {
148
+ console.log('window focus ~~~~~~');
149
+ });
150
+
115
151
  // bind auto loan renew events
116
152
  this.bindLoanRenewEvents();
117
153
 
118
154
  // localCache used for auto-loan-renew
119
155
  this.localCache = new LocalCache({
120
- namespace: 'loanRenew',
156
+ namespace: `loanRenew-${this.identifier || 'unknown_book'}`,
121
157
  });
122
158
 
123
- if (!this.sharedObserver) {
124
- this.sharedObserver = new SharedResizeObserver();
125
- this.setupResizeObserver();
126
- }
159
+ this.sharedObserver = new SharedResizeObserver();
160
+ this.setupResizeObserver();
161
+
162
+ const modal = this.modal;
163
+ console.log('modal', modal);
127
164
  }
128
165
 
129
166
  updated(changed) {
167
+ console.log('UPDATED', this.lendingStatus);
130
168
  if (changed.has('lendingStatus') || changed.has('bwbPurchaseUrl')) {
131
169
  this.setupLendingToolbarActions();
132
170
  this.update();
@@ -138,6 +176,19 @@ export default class IABookActions extends LitElement {
138
176
  }
139
177
  }
140
178
 
179
+ render() {
180
+ console.log("RENDER IABookActions", this.barType);
181
+ if (this.barType === 'title') {
182
+ return html`<section class="lending-wrapper">
183
+ ${this.bookTitleBar}
184
+ </section>`;
185
+ }
186
+
187
+ return html`<section class="lending-wrapper">
188
+ ${this.bookActionBar}
189
+ </section>`;
190
+ }
191
+
141
192
  /** SharedObserver resize handler */
142
193
  handleResize(entry) {
143
194
  // if you are observing multiple targets,
@@ -153,7 +204,8 @@ export default class IABookActions extends LitElement {
153
204
 
154
205
  /** Removes observer */
155
206
  disconnectResizeObserver() {
156
- this.sharedObserver?.removeObserver({
207
+ if (!this.sharedObserver) return;
208
+ this.sharedObserver.removeObserver({
157
209
  handler: this,
158
210
  target: this.shadowRoot.host,
159
211
  });
@@ -164,7 +216,9 @@ export default class IABookActions extends LitElement {
164
216
  setupResizeObserver() {
165
217
  if (!this.shadowRoot) return;
166
218
  this.sharedObserver?.addObserver({
167
- handler: this,
219
+ handler: {
220
+ handleResize: this.handleResize.bind(this),
221
+ },
168
222
  target: this.shadowRoot.host,
169
223
  });
170
224
  }
@@ -218,8 +272,9 @@ export default class IABookActions extends LitElement {
218
272
  }
219
273
 
220
274
  if (this.borrowType === 'browsed') {
275
+ console.log('this.borrowType', this.borrowType);
221
276
  // start timer for browsed.
222
- this.startBrowseTimer();
277
+ await this.startBrowseTimer();
223
278
  }
224
279
 
225
280
  // early return if not borrowed or no action-bar
@@ -233,6 +288,7 @@ export default class IABookActions extends LitElement {
233
288
  setTimeout(() => {
234
289
  this.resetTimerCountState();
235
290
  }, 100);
291
+ return;
236
292
  }
237
293
 
238
294
  /**
@@ -248,57 +304,26 @@ export default class IABookActions extends LitElement {
248
304
  * Bind 1 hour loan auto renew event,
249
305
  * There are two events we want to use,
250
306
  * 1. BookReader:userAction - dispatched from bookreader side
251
- * 2. click event to refresh the page when book is auto expired
252
- * 3. IABookActions:loanRenew - dispatched from timer-countdown component
307
+ * 2. IABookActions:loanRenew - dispatched from timer-countdown component
253
308
  */
254
309
  bindLoanRenewEvents() {
255
310
  /**
256
311
  * dispatched this event from bookreader page changed
257
312
  */
258
313
  window.addEventListener('BookReader:userAction', () => {
259
- console.log('** BR user Action', {
260
- suppressAutoRenew: this.suppressAutoRenew,
261
- borrowType: this.borrowType,
262
- });
263
-
264
- this.suppressToast = true;
265
- this.closeToastManager();
266
-
267
- if (this.suppressAutoRenew) {
268
- return;
269
- }
270
-
314
+ console.log('BookReader:userAction **** , ', this.borrowType);
271
315
  if (this.borrowType === 'browsed') {
272
316
  this.autoLoanRenewChecker(true);
273
317
  }
274
318
  });
275
319
 
276
- /**
277
- * detect click-event on document to close toast-template
278
- */
279
- document.addEventListener('click', e => {
280
- console.log('*** top level click handler to remove toast on click');
281
- if (this.loanRenewHelper && this.loanRenewResult.secondsLeft > 0) {
282
- this.suppressToast = true;
283
- }
284
-
285
- // reload when user click on book-page and book is EXPIRED
286
- const hasExpired =
287
- 'browsingExpired' in this.lendingStatus &&
288
- this.lendingStatus?.browsingExpired;
289
- if (hasExpired && e.target.nodeName !== 'IA-BOOK-ACTIONS') {
290
- window.location?.reload();
291
- }
292
- });
293
-
294
320
  /**
295
321
  * this event dispatched from timer-countdown component for:
296
- * 1. user turned book page after showing auto-returned warning message
297
- * 2. show warning message having remaining time
322
+ * 1. user turned book page after showing auto-returned warning modal
323
+ * 2. show warning modal having remaining time
298
324
  * @see TimerCountdown::timerCountdown
299
325
  */
300
326
  this.addEventListener('IABookActions:loanRenew', async event => {
301
- console.log('** IABookActions:loanRenew', event.detail);
302
327
  await this.autoLoanRenewChecker(false);
303
328
 
304
329
  /**
@@ -311,19 +336,22 @@ export default class IABookActions extends LitElement {
311
336
  let secondsLeft = event?.detail?.secondsLeft;
312
337
  if (secondsLeft < 50) {
313
338
  this.suppressAutoRenew = true;
339
+ console.log("BROWSER TIMER IN secondsLeft < 50!!",secondsLeft, secondsLeft < 50);
340
+ console.log("BROWSER TIMER IN secondsLeft < 50!! this.loanRenewResult",this.loanRenewResult);
341
+
314
342
  this.browseHasExpired();
315
343
  }
316
344
 
317
- // show warning message with remaining time to auto returned it.
345
+ // show warning modal with remaining time to auto returned it.
318
346
  if (this.loanRenewResult.renewNow === false) {
319
347
  /**
320
348
  * so compensate for the 50 second buffer to handle above race conditions
321
349
  * let's reduce 1 min from warning texts and early return the book when 1 min left.
322
350
  */
323
351
  secondsLeft -= 60;
324
-
325
352
  this.loanRenewResult.secondsLeft = secondsLeft;
326
- this.showToastMessage();
353
+
354
+ this.showWarningModal();
327
355
  }
328
356
  });
329
357
  }
@@ -335,29 +363,174 @@ export default class IABookActions extends LitElement {
335
363
  * @param {Boolean} hasPageChanged
336
364
  */
337
365
  async autoLoanRenewChecker(hasPageChanged = false) {
338
- this.loanRenewHelper = new LoanRenewHelper(
339
- hasPageChanged,
340
- this.identifier,
341
- this.localCache,
342
- this.loanRenewTimeConfig
343
- );
366
+ console.log('autoLoanRenewChecker &&&&', this.loanRenewHelper);
367
+ // something is up here
368
+ if (!this.loanRenewHelper) {
369
+ this.loanRenewHelper = new LoanRenewHelper(
370
+ hasPageChanged,
371
+ this.identifier,
372
+ this.localCache,
373
+ this.loanRenewTimeConfig
374
+ );
375
+ }
376
+
344
377
 
345
378
  await this.loanRenewHelper.handleLoanRenew();
346
379
  this.loanRenewResult = this.loanRenewHelper.result;
347
380
  }
348
381
 
349
382
  /**
383
+ * create / select the modal-manager component on DOM
384
+ * @memberof IABookActions
385
+ */
386
+ // async useModalManager() {
387
+ // if (!this.modal) {
388
+ // this.modal = document.querySelector('modal-manager');
389
+
390
+ // if (!this.modal) this.modal = document.createElement('modal-manager');
391
+ // }
392
+
393
+ // this.modal.id = 'action-bar-modal';
394
+ // await document.body.appendChild(this.modal);
395
+ // }
396
+
397
+ /**
398
+ * Show the waring modal to ask user if they are still reading
399
+ */
400
+ async showWarningModal() {
401
+ console.log('****** showWarningModal ****** clearing intervals');
402
+ // await this.useModalManager();
403
+ console.log('showWarningModal window?.IALendingInterval BEFORE', window?.IALendingIntervals);
404
+
405
+ window?.IALendingIntervals?.clearAll();
406
+ console.log('showWarningModal window?.IALendingIntervals AFTER', window?.IALendingIntervals);
407
+
408
+ // if secondsLeft < 60, consider it 1 minute
409
+ let { secondsLeft } = this.loanRenewResult;
410
+ if (secondsLeft === undefined) {
411
+ secondsLeft = this.lendingStatus.secondsLeftOnLoan;
412
+ } else {
413
+ secondsLeft = secondsLeft > 60 ? secondsLeft : 60;
414
+ }
415
+
416
+ const config = new ModalConfig();
417
+ config.headline = 'Are you still here?';
418
+ config.showCloseButton = false;
419
+ config.closeOnBackdropClick = false;
420
+ config.message = this.loanRenewHelper?.getMessageTexts(
421
+ this.loanRenewResult.texts,
422
+ secondsLeft
423
+ );
424
+
425
+ const customContent = html`<br />
426
+ <div id="custom-buttons" style="display:flex;justify-content:center;">
427
+ <button
428
+ style="${modalButtonStyle.iaButton} ${modalButtonStyle.renew}"
429
+ @click=${event => {
430
+ this.changeModalState(event);
431
+ this.autoLoanRenewChecker(true);
432
+ }}
433
+ >
434
+ Keep reading
435
+ </button>
436
+ <button
437
+ style="${modalButtonStyle.iaButton} ${modalButtonStyle.return}"
438
+ @click=${event => {
439
+ this.changeModalState(event);
440
+ document.querySelector('ia-book-actions').disableActionGroup = true;
441
+ this.returnNow = true;
442
+ }}
443
+ >
444
+ Return the book
445
+ </button>
446
+ </div> `;
447
+
448
+ this.modal.showModal({ config, customModalContent: customContent });
449
+ }
450
+
451
+ /**
452
+ * helper function to change button click states on modal-manager
453
+ * - add loader on button click
454
+ * - change button appearance
455
+ *
456
+ * @param {Event} event
457
+ */
458
+ changeModalState(event) {
459
+ if (event.target === undefined) return;
460
+
461
+ const button = event.target;
462
+ button.innerHTML = `<ia-activity-indicator
463
+ mode="processing"
464
+ style=${modalButtonStyle.loaderIcon}
465
+ ></ia-activity-indicator>`;
466
+
467
+ const parentElement = event.target.parentNode;
468
+ parentElement.style.pointerEvents = 'none';
469
+ parentElement.style.opacity = 0.8;
470
+ }
471
+
472
+ /**
473
+ * Show modal when book is auto returned
474
+ */
475
+ async showExpiredModal() {
476
+ // await this.useModalManager();
477
+
478
+ const config = new ModalConfig();
479
+ config.headline = '';
480
+ config.showCloseButton = false;
481
+ config.closeOnBackdropClick = false;
482
+ config.message = 'This book has been returned due to inactivity.';
483
+
484
+ const customContent = html`<br />
485
+ <div style="text-align: center">
486
+ <button
487
+ style="${modalButtonStyle.iaButton} ${modalButtonStyle.renew}"
488
+ @click=${() => {
489
+ window.location?.reload();
490
+ }}
491
+ >
492
+ Okay
493
+ </button>
494
+ </div> `;
495
+
496
+ this.modal?.showModal({ config, customModalContent: customContent });
497
+ }
498
+
499
+ /**
500
+ * close/hide modal-manager
501
+ */
502
+ async closeModalManager() {
503
+ this.modal = document.querySelector('modal-manager');
504
+ this.modal?.closeModal();
505
+ }
506
+
507
+ /**
508
+ * @deprecated
509
+ * create / select the toast-template component on DOM
510
+ *
511
+ * @memberof IABookActions
512
+ */
513
+ async useToastManager() {
514
+ if (!this.toast) {
515
+ this.toast = this.shadowRoot.querySelector('toast-template');
516
+
517
+ if (!this.toast) this.toast = document.createElement('toast-template');
518
+ }
519
+
520
+ await this.shadowRoot.appendChild(this.toast);
521
+ }
522
+
523
+ /**
524
+ * @deprecated
350
525
  * close/hide toast message
351
526
  */
352
527
  async closeToastManager() {
353
- console.log('** closeToastManager');
354
- const toastTemplate = this.shadowRoot.querySelector('toast-template');
355
- if (toastTemplate) {
356
- toastTemplate?.remove();
357
- }
528
+ this.toast = this.shadowRoot.querySelector('toast-template');
529
+ this.toast?.remove();
358
530
  }
359
531
 
360
532
  /**
533
+ * @deprecated
361
534
  * Show toast messages on some specific loan renew features. e.g.
362
535
  * - show message when book is auto renewed
363
536
  * - show message when book is expired
@@ -370,11 +543,7 @@ export default class IABookActions extends LitElement {
370
543
  });
371
544
  if (this.suppressToast) return;
372
545
 
373
- let toastTemplate = this.shadowRoot.querySelector('toast-template');
374
- if (!toastTemplate) {
375
- toastTemplate = document.createElement('toast-template');
376
- }
377
- await this.shadowRoot.appendChild(toastTemplate);
546
+ await this.useToastManager();
378
547
 
379
548
  // if secondsLeft < 60, consider it 1 minute
380
549
  let { secondsLeft } = this.loanRenewResult;
@@ -391,12 +560,15 @@ export default class IABookActions extends LitElement {
391
560
  secondsLeft
392
561
  );
393
562
 
394
- toastTemplate.showToast({
563
+ this.toast?.showToast({
395
564
  config,
396
565
  });
397
566
  }
398
567
 
399
568
  async browseHasRenew() {
569
+ console.log('****** browseHasRenew ******');
570
+ window?.IALendingIntervals?.clearAll();
571
+
400
572
  const loanTime = await this.localCache.get(`${this.identifier}-loanTime`);
401
573
  const secondsLeft = (loanTime - new Date()) / 1000; // different in seconds
402
574
 
@@ -407,30 +579,13 @@ export default class IABookActions extends LitElement {
407
579
  secondsLeftOnLoan: secondsLeft,
408
580
  };
409
581
  this.lendingStatus = currStatus;
410
-
411
- this.startBrowseTimer();
412
- }
413
-
414
- startBrowseTimer() {
415
- clearTimeout(this.browseTimer);
416
- const {
417
- browsingExpired,
418
- user_has_browsed,
419
- secondsLeftOnLoan,
420
- } = this.lendingStatus;
421
- if (!user_has_browsed || browsingExpired) {
422
- return;
423
- }
424
-
425
- this.browseTimer = setTimeout(() => {
426
- this.browseHasExpired();
427
- }, secondsLeftOnLoan * 1000);
428
582
  }
429
583
 
430
584
  /**
431
585
  * Execute when loan is expired
432
586
  */
433
587
  async browseHasExpired() {
588
+ console.log('browseHasExpired', window?.IALendingIntervals);
434
589
  window?.IALendingIntervals?.clearAll();
435
590
 
436
591
  const currStatus = { ...this.lendingStatus, browsingExpired: true };
@@ -443,14 +598,34 @@ export default class IABookActions extends LitElement {
443
598
  // show message after browsed book is expired.
444
599
  this.loanRenewResult.renewNow = false;
445
600
  this.loanRenewResult.texts =
446
- 'This book has been automatically returned due to inactivity.';
601
+ 'This book has been returned due to inactivity.';
447
602
 
448
- this.suppressToast = false;
449
- this.showToastMessage();
603
+ this.showExpiredModal();
450
604
 
451
605
  window?.Sentry?.captureMessage(sentryLogs.browseHasExpired);
452
606
  }
453
607
 
608
+ async startBrowseTimer() {
609
+ clearTimeout(this.browseTimer);
610
+
611
+ const {
612
+ browsingExpired,
613
+ user_has_browsed,
614
+ secondsLeftOnLoan,
615
+ } = this.lendingStatus;
616
+ if (!user_has_browsed || browsingExpired) {
617
+ console.log('user not browsed', this.lendingStatus);
618
+ return;
619
+ }
620
+
621
+ console.log('user browsed', this.lendingStatus);
622
+
623
+ this.browseTimer = setTimeout(() => {
624
+ console.log("BROWSER TIMER IN TIMEOUT!!");
625
+ this.browseHasExpired();
626
+ }, secondsLeftOnLoan * 1000);
627
+ }
628
+
454
629
  /**
455
630
  * Reset timer animation state after book renewed
456
631
  */
@@ -478,16 +653,19 @@ export default class IABookActions extends LitElement {
478
653
  }, 100);
479
654
  }
480
655
 
481
- render() {
482
- if (this.barType === 'title') {
483
- return html`<section class="lending-wrapper">
484
- ${this.bookTitleBar}
485
- </section>`;
656
+ get modal () {
657
+ let modal = document.querySelector('modal-manager');
658
+ if (!modal) {
659
+ const newModal = document.createElement('modal-manager');
660
+ document.body.appendChild(newModal);
661
+ modal = newModal;
486
662
  }
487
663
 
488
- return html`<section class="lending-wrapper">
489
- ${this.bookActionBar}
490
- </section>`;
664
+ if (modal && !modal.id !== 'action-bar-modal') {
665
+ modal.setAttribute('id', 'action-bar-modal');
666
+ }
667
+
668
+ return modal;
491
669
  }
492
670
 
493
671
  get bookTitleBar() {
@@ -498,6 +676,7 @@ export default class IABookActions extends LitElement {
498
676
  }
499
677
 
500
678
  get timerCountdownTemplate() {
679
+ console.log('timerCountdownTemplatetimerCountdownTemplate');
501
680
  return this.borrowType === 'browsed'
502
681
  ? html`<timer-countdown
503
682
  .secondsLeftOnLoan=${Number(this.lendingStatus.secondsLeftOnLoan)}
@@ -508,6 +687,7 @@ export default class IABookActions extends LitElement {
508
687
  }
509
688
 
510
689
  get bookActionBar() {
690
+ console.log('~~~~~');
511
691
  return html`
512
692
  <collapsible-action-group
513
693
  .userid=${this.userid}
@@ -524,6 +704,7 @@ export default class IABookActions extends LitElement {
524
704
  ?disabled=${this.disableActionGroup}
525
705
  ?autoRenew=${this.loanRenewResult.renewNow}
526
706
  ?autoReturn=${this.lendingStatus.browsingExpired}
707
+ ?returnNow=${this.returnNow}
527
708
  @loanAutoRenewed=${this.handleLoanAutoRenewed}
528
709
  @lendingActionError=${this.handleLendingActionError}
529
710
  @toggleActionGroup=${this.handleToggleActionGroup}
@@ -545,19 +726,21 @@ export default class IABookActions extends LitElement {
545
726
  if (this.loanRenewResult.renewNow) {
546
727
  window?.IALendingIntervals?.clearAll();
547
728
 
548
- this.suppressToast = false;
549
- await this.browseHasRenew();
550
- await this.showToastMessage();
551
- await this.resetTimerCountState();
552
-
553
- window?.Sentry?.captureMessage(sentryLogs.bookHasRenewed);
554
-
555
729
  // testing console....
556
730
  console.log('IABookActions: AutoRenewed:- ', {
557
731
  ajaxResponse: event?.detail?.data,
558
732
  loanRenewResult: this.loanRenewResult,
559
733
  secondsLeftOnLoan: Math.round(this.lendingStatus.secondsLeftOnLoan),
560
734
  });
735
+
736
+ await this.browseHasRenew();
737
+ await this.resetTimerCountState();
738
+
739
+ // close the modal
740
+ this.modal?.remove();
741
+ this.modal?.showModal({ config: {}, customModalContent: `` });
742
+
743
+ window?.Sentry?.captureMessage(sentryLogs.bookHasRenewed);
561
744
  }
562
745
  }
563
746
 
@@ -602,7 +785,7 @@ export default class IABookActions extends LitElement {
602
785
  this.disableActionGroup = !this.disableActionGroup;
603
786
  }
604
787
 
605
- /*
788
+ /**
606
789
  * handle lending errors occure during different operation like
607
790
  * browse book, borrow book, borrowed book token etc...
608
791
  *
@@ -656,16 +839,7 @@ export default class IABookActions extends LitElement {
656
839
 
657
840
  /* show error message if something went wrong */
658
841
  async showErrorModal(errorMsg, action) {
659
- // check if this.modal passed as prop
660
- if (!this.modal) {
661
- this.modal = document.querySelector('modal-manager');
662
-
663
- // check the DOM if <modal-manager> already there
664
- if (!this.modal) this.modal = document.createElement('modal-manager');
665
- }
666
-
667
- this.modal.id = 'action-bar-modal';
668
- await document.body.appendChild(this.modal);
842
+ // await this.useModalManager();
669
843
 
670
844
  const modalConfig = new ModalConfig({
671
845
  title: 'Lending error',
@@ -676,7 +850,7 @@ export default class IABookActions extends LitElement {
676
850
 
677
851
  if (action === 'create_token') {
678
852
  const refreshButton = html`<button
679
- style="background:none;font-size:inherit;border:0;padding:0;color:#0000ee;cursor:pointer;text-decoration:underline"
853
+ style="${modalButtonStyle.refresh}"
680
854
  @click=${() => window.location.reload(true)}
681
855
  >
682
856
  refresh
@@ -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
 
@@ -347,7 +349,7 @@ describe('Auto renew one hour loan', () => {
347
349
  });
348
350
  el.dispatchEvent(myEvent);
349
351
 
350
- await aTimeout(1500); // wait for 1.5 second
352
+ await aTimeout(1900); // wait for 1.9 second
351
353
  await el.updateComplete;
352
354
 
353
355
  expect(el.loanRenewResult.texts).to.equal(