@internetarchive/ia-book-actions 0.0.27-a1 → 0.0.28-alpha1

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
@@ -94,6 +94,12 @@
94
94
  canBrowseAndBorrow,
95
95
  } from '../src/assets/data/lending-get-status-service.js';
96
96
  import { ModalConfig } from '@internetarchive/modal-manager';
97
+ import { LocalCache } from '@internetarchive/local-cache';
98
+
99
+ const localCache = new LocalCache({
100
+ namespace: '1HourLoanStorage',
101
+ defaultTTL: 1 * 60, // 1 minute
102
+ });
97
103
 
98
104
  const userid = '@neeraj-archive';
99
105
  const identifier = 'naturalhistoryof00unse_4111';
@@ -121,8 +127,32 @@
121
127
  iaBookActions.lendingBarPostInit = lendingBarPostInit;
122
128
  iaBookActions.tokenDelay = 2000; // 2 seconds
123
129
  iaBookActions.returnUrl = 'https://openlibrary.org';
130
+ iaBookActions.localCache = localCache;
131
+ iaBookActions.autoRenewTimeConfig = {
132
+ totalTime: 60, // 60 second = 1 min
133
+ autoRenewCheckerAtLast: 10, // totalTime - 10th minute
134
+ autoRenewVisitedInLast: 15, // within last 15 second/minute
135
+ };
124
136
 
125
137
  let lendingStatus1 = iaBookActions.lendingStatus;
138
+ let secondsLeftOnLoan = 60;
139
+
140
+ document.querySelector('#flip-page').addEventListener('click', async (e) => {
141
+ try {
142
+ // set pageFlipTime time in localStorage
143
+ await localCache.set({
144
+ key: `${identifier}-pageFlipTime`,
145
+ value: new Date(), // current time
146
+ })
147
+
148
+ // dispatch event to say book page has flipped
149
+ document.querySelector("ia-book-actions").dispatchEvent(
150
+ new CustomEvent("bookPageFlip", {
151
+ detail: { bookPageChange: true },
152
+ })
153
+ );
154
+ } catch (error) {}
155
+ });
126
156
 
127
157
  document.querySelectorAll('.titleBar input[type=checkbox]').forEach((item) => {
128
158
  item.addEventListener('click', (e) => {
@@ -134,6 +164,21 @@
134
164
  });
135
165
  });
136
166
 
167
+ document.querySelectorAll('.autoRenew input[type=checkbox]').forEach((item) => {
168
+ item.addEventListener('click', (e) => {
169
+ if (e.target.checked) {
170
+ iaBookActions.autoRenew = true;
171
+ iaBookActions.resetTimerCountdown = true;
172
+ lendingStatus1.secondsLeftOnLoan = secondsLeftOnLoan;
173
+ } else {
174
+ iaBookActions.autoRenew = false;
175
+ }
176
+
177
+ let lending = {...defaultLendingStatus, ...lendingStatus1};
178
+ iaBookActions.lendingStatus = lending;
179
+ });
180
+ });
181
+
137
182
  document.querySelectorAll('.errorEnable input[type=checkbox]').forEach((item) => {
138
183
  if (window.location.href.indexOf('?error=true') !== -1) {
139
184
  item.checked = true;
@@ -174,8 +219,10 @@
174
219
  document.querySelectorAll('.printDisabled input[type=checkbox]').forEach((item) => {
175
220
  item.addEventListener('click', (e) => {
176
221
  if (e.target.checked) {
222
+ if (e.target.value === 'is_printdisabled') lendingStatus1.is_printdisabled = true;
177
223
  if (e.target.value === 'user_is_printdisabled') lendingStatus1.user_is_printdisabled = true;
178
224
  } else {
225
+ if (e.target.value === 'is_printdisabled') lendingStatus1.is_printdisabled = false;
179
226
  if (e.target.value === 'user_is_printdisabled') lendingStatus1.user_is_printdisabled = false;
180
227
  }
181
228
 
@@ -185,12 +232,23 @@
185
232
  });
186
233
 
187
234
  document.querySelectorAll('.availableToBrowse input[type=radio]').forEach((item) => {
188
- item.addEventListener('click', (e) => {
235
+ item.addEventListener('click', async (e) => {
189
236
  if (e.target.value === 'user_has_browsed') {
190
237
  lendingStatus1.user_has_browsed = true;
191
238
  lendingStatus1.available_to_browse = false;
192
- lendingStatus1.secondsLeftOnLoan = 10;
239
+ lendingStatus1.secondsLeftOnLoan = secondsLeftOnLoan;
193
240
  lendingStatus1.browsingExpired = false;
241
+
242
+ try {
243
+ // set a value
244
+ await localCache.set({
245
+ key: `${identifier}-loanTime`,
246
+ value: new Date(), // current time
247
+ })
248
+
249
+ await localCache.delete(`${identifier}-pageFlipTime`);
250
+ } catch (error) {}
251
+
194
252
  } else if(e.target.value === 'browsingExpired') {
195
253
  lendingStatus1.user_has_browsed = true;
196
254
  lendingStatus1.available_to_browse = false;
@@ -270,6 +328,10 @@
270
328
  <table class="initial">
271
329
  <tr colspan='5'>
272
330
  <td class="errorEnable"><label><input type="checkbox" name="errorEnable" value="errorEnable" />Do you want to see error response?</label></td>
331
+ <td class="autoRenew"><label><input type="checkbox" name="autoRenew" value="autoRenew" />Do you want to auto-renew 1 hour</label></td>
332
+ </tr>
333
+ <tr>
334
+ <td><button id="flip-page">Flip page</button></td>
273
335
  </tr>
274
336
  <tr>
275
337
  <td class="userState">
@@ -277,6 +339,7 @@
277
339
  <label><input type="checkbox" name="user" value="isLoggedIn" />isLoggedIn</label>
278
340
  </td>
279
341
  <td class="printDisabled">
342
+ <label><input type="checkbox" name="is_printdisabled" value="is_printdisabled" />is_printdisabled</label>
280
343
  <label><input type="checkbox" name="user_is_printdisabled" value="user_is_printdisabled" />user_is_printdisabled</label>
281
344
  </td>
282
345
  <td class="availableToBrowse">
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@internetarchive/ia-book-actions",
3
- "version": "0.0.27-a1",
3
+ "version": "0.0.28-alpha1",
4
4
  "description": "Bookreader actions for borrowable book",
5
5
  "author": "Internet Archive",
6
6
  "license": "AGPL-3.0-only",
@@ -19,10 +19,12 @@
19
19
  },
20
20
  "dependencies": {
21
21
  "@internetarchive/icon-info": "^1.2.6",
22
- "@internetarchive/local-cache": "^0.2.0",
22
+ "@internetarchive/local-cache": "^0.2.1",
23
23
  "@internetarchive/modal-manager": "^0.2.1",
24
24
  "@internetarchive/shared-resize-observer": "^0.2.0",
25
- "lit": "^2.2.1"
25
+ "add": "^2.0.6",
26
+ "lit": "^2.2.1",
27
+ "yarn": "^1.22.19"
26
28
  },
27
29
  "devDependencies": {
28
30
  "@open-wc/eslint-config": "^4.2.0",
@@ -0,0 +1,58 @@
1
+ import { css } from 'lit';
2
+
3
+ export default timerCountDownStyle = css`
4
+ :host {
5
+ right: 5px;
6
+ font-size: 14px;
7
+ position: absolute;
8
+ }
9
+ #countdown1 {
10
+ position: relative;
11
+ margin: auto;
12
+ margin-top: 100px;
13
+ height: 40px;
14
+ width: 40px;
15
+ text-align: center;
16
+ }
17
+
18
+ #countdown-number {
19
+ color: white;
20
+ display: inline-block;
21
+ line-height: 40px;
22
+ margin-right: 12px;
23
+ }
24
+
25
+ svg {
26
+ position: absolute;
27
+ top: 0;
28
+ right: 0;
29
+ width: 40px;
30
+ height: 40px;
31
+ transform: rotateY(-180deg) rotateZ(-90deg);
32
+ }
33
+
34
+ svg circle {
35
+ stroke-dasharray: 113px;
36
+ stroke-dashoffset: 0px;
37
+ stroke-linecap: round;
38
+ stroke-width: 3px;
39
+ stroke: white;
40
+ fill: none;
41
+ animation: countdown 60s linear infinite forwards;
42
+ }
43
+
44
+ @keyframes countdown {
45
+ from {
46
+ stroke-dashoffset: 0px;
47
+ }
48
+ to {
49
+ stroke-dashoffset: 113px;
50
+ }
51
+ }
52
+ .hidden {
53
+ display: none;
54
+ }
55
+ .visible {
56
+ display: inline-block;
57
+ }
58
+ `;
@@ -28,6 +28,7 @@ export class CollapsibleActionGroup extends ActionsHandler {
28
28
  dropdownArrow: { type: String },
29
29
  disabled: { type: Boolean },
30
30
  returnUrl: { type: String },
31
+ browseAgainNow: { type: Boolean },
31
32
  };
32
33
  }
33
34
 
@@ -47,6 +48,7 @@ export class CollapsibleActionGroup extends ActionsHandler {
47
48
  this.loaderIcon = 'https://archive.org/upload/images/tree/loading.gif';
48
49
  this.disabled = false;
49
50
  this.returnUrl = '';
51
+ this.browseAgainNow = false;
50
52
  }
51
53
 
52
54
  updated(changed) {
@@ -56,6 +58,16 @@ export class CollapsibleActionGroup extends ActionsHandler {
56
58
  ) {
57
59
  this.resetActions();
58
60
  }
61
+
62
+ if (this.browseAgainNow === true) {
63
+ this.dispatchEvent(
64
+ new CustomEvent('autoBrowseBook', {
65
+ detail: {
66
+ event: { 'autoRenewed1Hour': 'browseBook' },
67
+ },
68
+ })
69
+ );
70
+ }
59
71
  }
60
72
 
61
73
  /**
@@ -55,7 +55,7 @@ export class InfoIcon extends LitElement {
55
55
  .more-info-icon.mobile {
56
56
  position: absolute;
57
57
  top: 4px;
58
- right: 10px;
58
+ right: 55px;
59
59
  margin-top: 15px;
60
60
  }
61
61
  .hidden {
@@ -0,0 +1,127 @@
1
+ import { html, css, LitElement } from 'lit';
2
+
3
+ export default class TimerCountdown extends LitElement {
4
+ static get properties() {
5
+ return {
6
+ time: { type: Number },
7
+ autoRenewCheckerAtLast: { type: Number },
8
+ resetTimerCountdown: { type: Boolean },
9
+ };
10
+ }
11
+
12
+ constructor() {
13
+ super();
14
+ this.time = 60;
15
+ this.autoRenewCheckerAtLast = 0;
16
+ this.timerInterval = undefined;
17
+ this.resetTimerCountdown = false;
18
+ }
19
+
20
+ disconnectedCallback() {
21
+ clearInterval(this.timerInterval)
22
+ }
23
+
24
+ firstUpdated() {
25
+ this.timerCountdown();
26
+ }
27
+
28
+ updated(changed) {
29
+ if (changed.has('resetTimerCountdown')) {
30
+ clearInterval(this.timerInterval);
31
+ this.timerCountdown()
32
+ }
33
+ }
34
+
35
+ timerCountdown() {
36
+ this.timerInterval = setInterval(() => {
37
+ this.time -= 1;
38
+
39
+ // execute 50th minute check
40
+ if (this.time === this.autoRenewCheckerAtLast) {
41
+ this.dispatchEvent(
42
+ new CustomEvent('autoReturnWarning', {
43
+ detail: {
44
+ remainingT: this.time
45
+ },
46
+ bubbles: true,
47
+ composed: true,
48
+ })
49
+ );
50
+ }
51
+
52
+ if (this.time === 0) clearInterval(this.timerInterval);
53
+
54
+ }, 1000);
55
+ }
56
+
57
+
58
+ render() {
59
+ return html`<div id="countdown">
60
+ <div id="countdown-number">${this.time}</div>
61
+ <svg>
62
+ <circle r="18" cx="20" cy="20"></circle>
63
+ </svg>
64
+ </div>`
65
+ }
66
+
67
+ static get styles() {
68
+ return css`
69
+ :host {
70
+ right: 5px;
71
+ font-size: 14px;
72
+ position: absolute;
73
+ }
74
+ #countdown1 {
75
+ position: relative;
76
+ margin: auto;
77
+ margin-top: 100px;
78
+ height: 40px;
79
+ width: 40px;
80
+ text-align: center;
81
+ }
82
+
83
+ #countdown-number {
84
+ color: white;
85
+ display: inline-block;
86
+ line-height: 40px;
87
+ margin-right: 12px;
88
+ }
89
+
90
+ svg {
91
+ position: absolute;
92
+ top: 0;
93
+ right: 0;
94
+ width: 40px;
95
+ height: 40px;
96
+ transform: rotateY(-180deg) rotateZ(-90deg);
97
+ }
98
+
99
+ svg circle {
100
+ stroke-dasharray: 113px;
101
+ stroke-dashoffset: 0px;
102
+ stroke-linecap: round;
103
+ stroke-width: 3px;
104
+ stroke: white;
105
+ fill: none;
106
+ animation: countdown 60s linear infinite forwards;
107
+ }
108
+
109
+ @keyframes countdown {
110
+ from {
111
+ stroke-dashoffset: 0px;
112
+ }
113
+ to {
114
+ stroke-dashoffset: 113px;
115
+ }
116
+ }
117
+ .hidden {
118
+ display: none;
119
+ }
120
+ .visible {
121
+ display: inline-block;
122
+ }
123
+ `;
124
+ }
125
+ }
126
+
127
+ window.customElements.define('timer-countdown', TimerCountdown);
@@ -0,0 +1,60 @@
1
+ import { html, css, LitElement, nothing } from 'lit';
2
+
3
+ export class ToastMessage extends LitElement {
4
+ static get properties() {
5
+ return {
6
+ texts: { type: String },
7
+ dismisOnClick: { type: Function },
8
+ hideToast: { type: Boolean },
9
+ };
10
+ }
11
+
12
+ constructor() {
13
+ super();
14
+ this.texts = '';
15
+ this.textClass = '';
16
+ this.hideToast = false;
17
+ }
18
+
19
+ render() {
20
+ return !this.hideToast
21
+ ? html`
22
+ <span class="toast-message ${this.textClass}">${this.texts}</span>
23
+ `
24
+ : nothing;
25
+ }
26
+
27
+ static get styles() {
28
+ const heightFromTop = css`var(--messageHeightFromTop, 15%)`;
29
+
30
+ return css`
31
+ :host {
32
+ display: inline-block;
33
+ }
34
+ .toast-message {
35
+ z-index: 2;
36
+ position: absolute;
37
+ top: ${heightFromTop};
38
+ font-size: 1.4rem;
39
+ color: #fff;
40
+ left: 50%;
41
+ right: 50%;
42
+ background: #333;
43
+ border: 1px solid #fff;
44
+ border-radius: 5px;
45
+ box-shadow: 1px 1px 2px;
46
+ padding: 10px;
47
+ width: fit-content;
48
+ transform: translate(-50%, -50%);
49
+ }
50
+ .hidden {
51
+ display: none;
52
+ }
53
+ .visible {
54
+ display: inline-block;
55
+ }
56
+ `;
57
+ }
58
+ }
59
+
60
+ window.customElements.define('toast-message', ToastMessage);
@@ -1,5 +1,6 @@
1
1
  import { LitElement } from 'lit';
2
2
 
3
+ import { LocalCache } from '@internetarchive/local-cache';
3
4
  import { URLHelper } from '../../config/url-helper.js';
4
5
  import ActionsHandlerService from './actions-handler-service.js';
5
6
  import * as Cookies from '../doc-cookies.js';
@@ -17,8 +18,14 @@ export default class ActionsHandler extends LitElement {
17
18
  super();
18
19
  this.identifier = '';
19
20
  this.ajaxTimeout = 6000;
20
- this.bindEvents();
21
21
  this.returnUrl = '';
22
+
23
+ this.localCache = new LocalCache({
24
+ namespace: '1HourLoanStorage',
25
+ defaultTTL: 1 * 30, // 0.5 minute
26
+ });
27
+
28
+ this.bindEvents();
22
29
  }
23
30
 
24
31
  sendEvent(eventCategory, eventAction) {
@@ -29,7 +36,9 @@ export default class ActionsHandler extends LitElement {
29
36
  );
30
37
  }
31
38
 
32
- bindEvents() {
39
+ async bindEvents() {
40
+ // console.log(await this.localCache.get('loanTime'));
41
+
33
42
  this.addEventListener('browseBook', ({ detail }) => {
34
43
  this.handleBrowseIt();
35
44
  this.setConsecutiveLoanCounts();
@@ -44,6 +53,14 @@ export default class ActionsHandler extends LitElement {
44
53
  this.sendEvent(category, action);
45
54
  });
46
55
 
56
+ this.addEventListener('autoBrowseBook', ({ detail }) => {
57
+ console.log('book has be renewed for you. thank you!!!');
58
+ this.handleAutoBrowseIt();
59
+ // this.setConsecutiveLoanCounts();
60
+ const { category, action } = detail.event;
61
+ this.sendEvent(category, action);
62
+ });
63
+
47
64
  this.addEventListener('returnNow', ({ detail }) => {
48
65
  this.handleReturnIt();
49
66
  const { category, action } = detail.event;
@@ -98,6 +115,7 @@ export default class ActionsHandler extends LitElement {
98
115
  handleBrowseIt() {
99
116
  const action = 'browse_book';
100
117
  this.dispatchToggleActionGroup();
118
+ // this.setBrowseTimeSession()
101
119
 
102
120
  ActionsHandlerService({
103
121
  action,
@@ -111,6 +129,21 @@ export default class ActionsHandler extends LitElement {
111
129
  });
112
130
  }
113
131
 
132
+ handleAutoBrowseIt() {
133
+ const action = 'auto_browse';
134
+ // this.setBrowseTimeSession()
135
+
136
+ ActionsHandlerService({
137
+ action,
138
+ identifier: this.identifier,
139
+ success: () => {},
140
+ error: data => {
141
+ this.dispatchActionError(action, data);
142
+ },
143
+ });
144
+ }
145
+
146
+
114
147
  handleReturnIt() {
115
148
  const action = 'return_loan';
116
149
  this.dispatchToggleActionGroup();
@@ -280,4 +313,16 @@ export default class ActionsHandler extends LitElement {
280
313
  '.archive.org'
281
314
  );
282
315
  }
316
+
317
+ async setBrowseTimeSession() {
318
+ try {
319
+ // set a value
320
+ await this.localCache.set({
321
+ key: `${this.identifier}-loanTime`,
322
+ value: new Date(), // current time
323
+ })
324
+
325
+ await this.localCache.delete(`${this.identifier}-pageFlipTime`);
326
+ } catch (error) {}
327
+ }
283
328
  }
@@ -146,17 +146,9 @@ export class GetLendingActions {
146
146
  }
147
147
 
148
148
  borrowPrintDisabledAction() {
149
- const lendingStatus = this.lendingStatus || {};
150
- const browsingHasExpired =
151
- !lendingStatus.available_to_browse && lendingStatus.browsingExpired;
152
-
153
- // one hour borrow config
154
- const oneHrBorrow = browsingHasExpired
155
- ? this.actionsConfig.browseAgainConfig()
156
- : this.actionsConfig.firstBrowseConfig();
157
149
  return {
158
150
  primaryTitle: bookTitles.eligible_pd,
159
- primaryActions: [oneHrBorrow, this.actionsConfig.borrowBookConfig()],
151
+ primaryActions: [this.actionsConfig.borrowBookConfig()],
160
152
  primaryColor: 'primary',
161
153
  secondaryActions: [
162
154
  this.actionsConfig.adminAccessConfig(),
@@ -409,7 +401,8 @@ export class GetLendingActions {
409
401
  const notBorrowable =
410
402
  !lendingStatus.available_to_borrow && !lendingStatus.available_to_browse;
411
403
 
412
- const userCanAccessPrintDisabled = lendingStatus.user_is_printdisabled;
404
+ const userCanAccessPrintDisabled =
405
+ lendingStatus.is_printdisabled && lendingStatus.user_is_printdisabled;
413
406
 
414
407
  const canBorrow =
415
408
  (lendingStatus.available_to_browse ||
@@ -21,9 +21,14 @@ export class LoanTokenPoller {
21
21
  disconnectedInterval() {
22
22
  clearInterval(this.loanTokenInterval);
23
23
  this.loanTokenInterval = undefined;
24
- if (window.Sentry) {
25
- window?.Sentry?.captureMessage('loan token interval cleared');
26
- }
24
+
25
+ // temporary disabling sentry notice
26
+ // as message is informational, not critical nor an error.
27
+ // also, as of 11/28/2022, JS build maps are not working properly on petabox
28
+ // and as a result, this comment is not properly grouped.
29
+ // if (window.Sentry) {
30
+ // window?.Sentry?.captureMessage('loan token interval cleared');
31
+ // }
27
32
  }
28
33
 
29
34
  async enableBookAccess() {
@@ -83,7 +88,10 @@ export class LoanTokenPoller {
83
88
  identifier: this.identifier,
84
89
  action,
85
90
  error: data => {
86
- window?.Sentry?.captureMessage('handleLoanTokenPoller error');
91
+ window?.Sentry?.captureMessage(
92
+ 'handleLoanTokenPoller error',
93
+ JSON.stringify(data)
94
+ );
87
95
  this.disconnectedInterval(); // stop token fetch api
88
96
  this.errorCallback({ detail: { action, data } });
89
97
  // send LendingServiceError to GA
@@ -0,0 +1,134 @@
1
+ import { nothing } from 'lit';
2
+
3
+ import ActionsHandlerService from './actions-handler/actions-handler-service.js';
4
+ import * as Cookies from './doc-cookies.js';
5
+
6
+ /**
7
+ * This class is used to create loan token for borrowed books
8
+ *
9
+ * ActionsHandlerService is a function being used to execute
10
+ */
11
+ export class OneHourAutoRenew {
12
+ constructor(pageClick, identifier, localCache, autoRenewTimeConfig) {
13
+ this.pageClick = pageClick;
14
+ this.identifier = identifier;
15
+ this.localCache = localCache;
16
+ this.autoRenewTimeConfig = autoRenewTimeConfig; // callback function to be called after loan token is created
17
+
18
+ this.autoReturnMessage = 'This book will be automatically returned in 15 seconds unless you turn a page.';
19
+ this.autoRenewMessage = 'This book has been renewed for another hour.';
20
+
21
+
22
+ this.resultRes = {
23
+ toastTexts: '',
24
+ hideToast: true,
25
+ };
26
+
27
+ return this.dd();
28
+ }
29
+
30
+ async dd() {
31
+ try {
32
+ const currentDate = new Date();
33
+
34
+ const {
35
+ totalTime, // 60
36
+ autoRenewCheckerAtLast, // 50
37
+ autoRenewVisitedInLast, // 15
38
+ } = this.autoRenewTimeConfig;
39
+
40
+ const loanTime = await this.localCache.get(`${this.identifier}-loanTime`);
41
+ const lastPageFlipTime = await this.localCache.get(`${this.identifier}-pageFlipTime`);
42
+
43
+ const lastFlipTimeFrame = await this.changeTime(
44
+ currentDate,
45
+ autoRenewVisitedInLast,
46
+ 'sub'
47
+ ); // 15 seconds
48
+
49
+ console.log(lastPageFlipTime, lastFlipTimeFrame)
50
+
51
+ // if page is not flipped in last 15 minutes,
52
+ // instantaly renew it if user viewed new page in last 10 minutes
53
+ if (this.pageClick === true) {
54
+ const lastTimeFrame = await this.changeTime(
55
+ loanTime,
56
+ totalTime - autoRenewCheckerAtLast,
57
+ 'add'
58
+ ); // 50 seconds
59
+ console.log('page flipped!')
60
+
61
+ if (currentDate >= lastTimeFrame) {
62
+ console.log('1 YES NOW BORROW IT AGAIN NOW!');
63
+ this.toastTexts = this.autoRenewMessage;
64
+ this.hideToast = false;
65
+ }
66
+
67
+ return nothing // early return
68
+ }
69
+
70
+ console.log(lastPageFlipTime, lastFlipTimeFrame)
71
+ if (lastPageFlipTime === undefined) { // not viewed
72
+ console.log('2 DON\'T BORROW IT!');
73
+ this.resultRes.toastTexts = this.autoReturnMessage;
74
+ this.resultRes.hideToast = false;
75
+ } else if (lastPageFlipTime >= lastFlipTimeFrame) { // viewed in last time frame
76
+ console.log('3 YES BORROW IT AGAIN!');
77
+ this.resultRes.toastTexts = this.autoRenewMessage;
78
+ this.resultRes.hideToast = false;
79
+ this.toastTexts = this.autoRenewMessage;
80
+ // this.browseAgainNow = true;
81
+ this.hideToast = false;
82
+ // await this.browseHasRenew();
83
+ } else if (lastPageFlipTime <= lastFlipTimeFrame) { // viewed but not in last time frame
84
+ console.log('4 DON\'T BORROW IT!');
85
+ this.resultRes.toastTexts = this.autoReturnMessage;
86
+ this.resultRes.hideToast = false;
87
+ }
88
+
89
+ // console.log(this.resultRes)
90
+ return this.resultRes;
91
+
92
+ } catch (error) {
93
+ console.log(error)
94
+ }
95
+ }
96
+
97
+ async changeTime(date, minutes, op) {
98
+ if (date === undefined) return undefined;
99
+
100
+ if (op === 'sub') {
101
+ return new Date(date.getTime() - minutes * 1000);
102
+ }
103
+
104
+ return new Date(date.getTime() + minutes * 1000);
105
+ }
106
+
107
+ sendEvent(eventCategory, eventAction) {
108
+ window?.archive_analytics?.send_event(
109
+ eventCategory,
110
+ eventAction,
111
+ `identifier=${this.identifier}`
112
+ );
113
+ }
114
+
115
+ async handleAutoRenew() {
116
+ const action = 'renew_loan';
117
+ ActionsHandlerService({
118
+ identifier: this.identifier,
119
+ action,
120
+ error: data => {
121
+ window?.Sentry?.captureMessage(
122
+ 'handleAutoRenew error',
123
+ JSON.stringify(data)
124
+ );
125
+ // this.errorCallback({ detail: { action, data } });
126
+ // send LendingServiceError to GA
127
+ this.sendEvent('handleAutoRenew', action);
128
+ },
129
+ success: () => {
130
+ return 'success';
131
+ },
132
+ });
133
+ }
134
+ }
@@ -1,5 +1,5 @@
1
1
  /* eslint-disable camelcase */
2
- import { html, css, LitElement } from 'lit';
2
+ import { html, css, LitElement, nothing } from 'lit';
3
3
 
4
4
  import { SharedResizeObserver } from '@internetarchive/shared-resize-observer';
5
5
  import { ModalConfig } from '@internetarchive/modal-manager';
@@ -8,10 +8,13 @@ import './components/collapsible-action-group.js';
8
8
  import './components/book-title-bar.js';
9
9
  import './components/text-group.js';
10
10
  import './components/info-icon.js';
11
+ import './components/timer-countdown.js';
12
+ import './components/toast-message.js';
11
13
 
12
14
  import { GetLendingActions } from './core/services/get-lending-actions.js';
13
15
  import { mobileContainerWidth } from './core/config/constants.js';
14
16
  import { LoanTokenPoller } from './core/services/loan-token-poller.js';
17
+ import { OneHourAutoRenew } from './core/services/one-hour-auto-renew.js';
15
18
 
16
19
  export const events = {
17
20
  browseExpired: 'IABookReader:BrowsingHasExpired',
@@ -32,10 +35,17 @@ export default class IABookActions extends LitElement {
32
35
  attribute: false,
33
36
  },
34
37
  barType: { type: String },
38
+ autoRenew: { type: Boolean },
35
39
  sharedObserver: { attribute: false },
36
40
  disableActionGroup: { type: Boolean },
37
41
  modal: { Object },
38
42
  tokenDelay: { type: Number },
43
+ toastTexts: { type: String },
44
+ remainingTime: { type: Number },
45
+ resetTimerCountdown: { type: Boolean },
46
+ localCache: { type: Object },
47
+ autoRenewTimeConfig: { type: Object },
48
+ browseAgainNow: { type: Boolean },
39
49
  };
40
50
  }
41
51
 
@@ -50,10 +60,13 @@ export default class IABookActions extends LitElement {
50
60
  this.bwbPurchaseUrl = '';
51
61
  this.lendingBarPostInit = () => {};
52
62
  this.barType = 'action'; // 'title'|'action'
63
+ this.autoRenew = false;
53
64
  this.sharedObserver = undefined;
54
65
  this.disableActionGroup = false;
55
66
  this.modal = undefined;
56
67
  this.tokenDelay = 120000; // 2 minutes
68
+ this.localCache = {},
69
+ this.autoRenewTimeConfig = {},
57
70
 
58
71
  // private props
59
72
  this.primaryActions = [];
@@ -63,6 +76,43 @@ export default class IABookActions extends LitElement {
63
76
  this.lendingOptions = {};
64
77
  this.borrowType = ''; // (browsed|borrowed)
65
78
  this.consecutiveLoanCounts = 1; // consecutive loan count
79
+ this.browseAgainNow = false;
80
+
81
+ // show toast-message for auto return message
82
+ this.hideToast = true;
83
+ this.autoReturnMessage = 'This book will be automatically returned in 15 seconds unless you turn a page.';
84
+ this.autoRenewMessage = 'This book has been renewed for another hour.';
85
+
86
+ // dispatched this event from timer-countdown
87
+ this.addEventListener('autoReturnWarning', event => {
88
+ // const dd = this.autoRenew1 = new OneHourAutoRenew(
89
+ // event.detail.bookPageChange,
90
+ // this.identifier,
91
+ // this.localCache,
92
+ // this.autoRenewTimeConfig,
93
+ // );
94
+ // this.toastTexts = dd.resultRes.toastTexts;
95
+ // this.hideToast = dd.resultRes.hideToast;
96
+ this.handleBookAutoRenew(event);
97
+ });
98
+
99
+ // dispatched this event from bookreader side
100
+ this.addEventListener('bookPageFlip', event => {
101
+ // console.log(e)
102
+ // const dd = this.autoRenew1 = new OneHourAutoRenew(
103
+ // event.detail.bookPageChange,
104
+ // this.identifier,
105
+ // this.localCache,
106
+ // this.autoRenewTimeConfig,
107
+ // );
108
+ // this.toastTexts = dd.resultRes.toastTexts;
109
+ // this.hideToast = dd.resultRes.hideToast;
110
+ // console.log(dd.resultRes)
111
+ this.handleBookAutoRenew(event);
112
+ // this.browseAgainNow = true;
113
+ });
114
+
115
+ this.resetTimerCountdown = false;
66
116
  }
67
117
 
68
118
  disconnectedCallback() {
@@ -84,15 +134,30 @@ export default class IABookActions extends LitElement {
84
134
  this.update();
85
135
  }
86
136
 
137
+ if (changed.has('autoRenew')) {
138
+ this.resetTimerCountdown = true;
139
+ }
87
140
  if (changed.has('sharedObserver')) {
88
141
  this.disconnectResizeObserver();
89
142
  this.setupResizeObserver();
90
143
  }
91
144
  }
92
145
 
93
- browseHasExpired() {
146
+ async browseHasExpired() {
94
147
  const currStatus = { ...this.lendingStatus, browsingExpired: true };
95
148
  this.lendingStatus = currStatus;
149
+
150
+ // remove respected key:value
151
+ await this.localCache.delete(`${this.identifier}-loanTime`);
152
+ await this.localCache.delete(`${this.identifier}-pageFlipTime`);
153
+
154
+ // show message after browsed book is expired.
155
+ this.toastTexts = 'This book has been automatically returned due to inactivity.';
156
+ }
157
+
158
+ async browseHasRenew() {
159
+ const currStatus = { ...this.lendingStatus, secondsLeftOnLoan: 61 };
160
+ this.lendingStatus = currStatus;
96
161
  }
97
162
 
98
163
  startBrowseTimer() {
@@ -105,12 +170,90 @@ export default class IABookActions extends LitElement {
105
170
  return;
106
171
  }
107
172
 
173
+ // expired book and show browse again button
108
174
  const timeLeft = secondsLeftOnLoan * 1000;
109
175
  setTimeout(() => {
110
176
  this.browseHasExpired();
111
177
  }, timeLeft);
112
178
  }
113
179
 
180
+ async handleBookAutoRenew(event) {
181
+ try {
182
+ const bookPageChange = event.detail.bookPageChange;
183
+ const currentDate = new Date();
184
+
185
+ const {
186
+ totalTime, // 60
187
+ autoRenewCheckerAtLast, // 50
188
+ autoRenewVisitedInLast, // 15
189
+ } = this.autoRenewTimeConfig;
190
+
191
+ const loanTime = await this.localCache.get(`${this.identifier}-loanTime`);
192
+ const lastPageFlipTime = await this.localCache.get(`${this.identifier}-pageFlipTime`);
193
+
194
+ const lastFlipTimeFrame = await this.changeTime(
195
+ currentDate,
196
+ autoRenewVisitedInLast,
197
+ 'sub'
198
+ ); // 15 seconds
199
+
200
+ console.log(lastPageFlipTime, lastFlipTimeFrame)
201
+
202
+ // if page is not flipped in last 15 minutes,
203
+ // instantaly renew it if user viewed new page in last 10 minutes
204
+ if (bookPageChange === true) {
205
+ const lastTimeFrame = await this.changeTime(
206
+ loanTime,
207
+ totalTime - autoRenewCheckerAtLast,
208
+ 'add'
209
+ ); // 50 seconds
210
+ console.log('page flipped!')
211
+
212
+ if (currentDate >= lastTimeFrame) {
213
+ console.log('1 YES NOW BORROW IT AGAIN NOW!');
214
+ this.toastTexts = this.autoRenewMessage;
215
+ this.browseAgainNow = true;
216
+ this.hideToast = false;
217
+ await this.browseHasRenew();
218
+ }
219
+
220
+ return nothing // early return
221
+ }
222
+
223
+ console.log(lastPageFlipTime, lastFlipTimeFrame)
224
+ if (lastPageFlipTime === undefined) { // not viewed
225
+ console.log('2 DON\'T BORROW IT!');
226
+ this.toastTexts = this.autoReturnMessage;
227
+ this.hideToast = false;
228
+ } else if (lastPageFlipTime >= lastFlipTimeFrame) { // viewed in last time frame
229
+ console.log('3 YES BORROW IT AGAIN!');
230
+ this.toastTexts = this.autoRenewMessage;
231
+ this.browseAgainNow = true;
232
+ this.hideToast = false;
233
+ await this.browseHasRenew();
234
+ } else if (lastPageFlipTime <= lastFlipTimeFrame) { // viewed but not in last time frame
235
+ console.log('4 DON\'T BORROW IT!');
236
+ this.toastTexts = this.autoReturnMessage;
237
+ this.hideToast = false;
238
+ } else {
239
+ this.browseAgainNow = true;
240
+ console.log('5 YES NOW BORROW IT AGAIN NOW!');
241
+ }
242
+ } catch (error) {
243
+ console.log(error)
244
+ }
245
+ }
246
+
247
+ async changeTime(date, minutes, op) {
248
+ if (date === undefined) return undefined;
249
+
250
+ if (op === 'sub') {
251
+ return new Date(date.getTime() - minutes * 1000);
252
+ }
253
+
254
+ return new Date(date.getTime() + minutes * 1000);
255
+ }
256
+
114
257
  /** SharedObserver resize handler */
115
258
  handleResize(entry) {
116
259
  // if you are observing multiple targets,
@@ -164,6 +307,7 @@ export default class IABookActions extends LitElement {
164
307
  });
165
308
 
166
309
  this.borrowType = actions.borrowType;
310
+ this.remainingTime = this.lendingStatus.secondsLeftOnLoan;
167
311
 
168
312
  const hasExpired =
169
313
  'browsingExpired' in this.lendingStatus &&
@@ -216,6 +360,26 @@ export default class IABookActions extends LitElement {
216
360
  ></book-title-bar>`;
217
361
  }
218
362
 
363
+ get timerCountdownTemplate() {
364
+ return this.borrowType === 'browsed'
365
+ ? html`<timer-countdown
366
+ time=${Number(this.remainingTime)}
367
+ .autoRenewCheckerAtLast=${this.autoRenewTimeConfig.autoRenewCheckerAtLast}
368
+ ?resetTimerCountdown=${this.resetTimerCountdown}
369
+ ></timer-countdown>`
370
+ : nothing;
371
+ }
372
+
373
+ get toastMessageTemplate() {
374
+ return html`
375
+ <toast-message
376
+ .texts=${this.toastTexts}
377
+ .dismisOnClick="1"
378
+ ?hideToast=${this.hideToast}
379
+ ></toast-message>
380
+ `;
381
+ }
382
+
219
383
  get bookActionBar() {
220
384
  return html`
221
385
  <collapsible-action-group
@@ -227,13 +391,16 @@ export default class IABookActions extends LitElement {
227
391
  .width=${this.width}
228
392
  .borrowType=${this.borrowType}
229
393
  .returnUrl=${this.returnUrl}
394
+ .autoRenew=${this.autoRenew}
230
395
  ?hasAdminAccess=${this.hasAdminAccess}
231
396
  ?disabled=${this.disableActionGroup}
397
+ ?browseAgainNow=${this.browseAgainNow}
232
398
  @lendingActionError=${this.handleLendingActionError}
233
399
  @toggleActionGroup=${this.handleToggleActionGroup}
234
400
  >
235
401
  </collapsible-action-group>
236
402
  ${this.textGroupTemplate} ${this.infoIconTemplate}
403
+ ${this.timerCountdownTemplate} ${this.toastMessageTemplate}
237
404
  `;
238
405
  }
239
406
 
@@ -401,6 +568,10 @@ export default class IABookActions extends LitElement {
401
568
  justify-content: center;
402
569
  flex-wrap: wrap;
403
570
  }
571
+
572
+ toast-message {
573
+ --messageHeightFromTop: 18%;
574
+ }
404
575
  `;
405
576
  }
406
577
  }
@@ -1,146 +0,0 @@
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>