@internetarchive/ia-book-actions 0.0.29-alpha2 → 0.0.29-alpha20

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
@@ -15,7 +15,7 @@
15
15
  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
16
16
  }
17
17
  #demo {
18
- height: 50px;
18
+ height: 100px;
19
19
  }
20
20
  ia-book-actions {
21
21
  --white: #fff;
@@ -87,6 +87,7 @@
87
87
 
88
88
  <script type="module">
89
89
  /* eslint-disable */
90
+
90
91
  import '../src/ia-book-actions.js';
91
92
  import {
92
93
  defaultLendingStatus,
@@ -96,7 +97,7 @@
96
97
  import { LocalCache } from '@internetarchive/local-cache';
97
98
 
98
99
  const localCache = new LocalCache({
99
- namespace: '1HourLoanStorage',
100
+ namespace: 'loanRenew',
100
101
  defaultTTL: 1 * 60, // 1 minute
101
102
  });
102
103
 
@@ -104,10 +105,6 @@
104
105
  const identifier = 'naturalhistoryof00unse_4111';
105
106
  let bwbPurchaseUrl = 'https://www.betterworldbooks.com/product/detail/cambridge-ancient-hist-v04-0521044863';
106
107
 
107
- let iaBookActions = document.querySelector('ia-book-actions');
108
-
109
- // for debugging
110
- window.iaBookActions = iaBookActions;
111
108
 
112
109
  const modalConfig = new ModalConfig();
113
110
  modalConfig.headerColor = '#d9534f';
@@ -117,6 +114,8 @@
117
114
  }
118
115
 
119
116
  // set defaultLendingStatus for unavailable (without borrowables) book
117
+ let iaBookActions = document.querySelector('ia-book-actions');
118
+
120
119
  iaBookActions.userid = userid;
121
120
  iaBookActions.identifier = identifier;
122
121
  iaBookActions.bookTitle = 'Contemporary Black biography. Volume 39 : profiles from the interContemporary Black biography. Volume 39';
@@ -127,14 +126,14 @@
127
126
  iaBookActions.tokenDelay = 2000; // 2 seconds
128
127
  iaBookActions.returnUrl = 'https://openlibrary.org';
129
128
  iaBookActions.localCache = localCache;
130
- iaBookActions.autoRenewTimeConfig = {
131
- totalTime: 60, // 60 second = 1 min
132
- autoCheckAt: 55, // totalTime - 10th minute
133
- FlippedInLast: 15, // within last 15 second/minute
129
+ iaBookActions.loanRenewConfig = {
130
+ totalTime: 40, // 60 second = 1 min
131
+ autoCheckAt: 35, // totalTime - 10th minute
132
+ pageChangedInLast: 5, // within last 15 second/minute
134
133
  };
135
134
 
136
135
  let lendingStatus1 = iaBookActions.lendingStatus;
137
- let secondsLeftOnLoan = 60;
136
+ let secondsLeftOnLoan = iaBookActions.loanRenewConfig.totalTime;
138
137
 
139
138
  document.querySelectorAll('.titleBar input[type=checkbox]').forEach((item) => {
140
139
  item.addEventListener('click', (e) => {
@@ -146,21 +145,6 @@
146
145
  });
147
146
  });
148
147
 
149
- document.querySelectorAll('.autoRenew input[type=checkbox]').forEach((item) => {
150
- item.addEventListener('click', (e) => {
151
- if (e.target.checked) {
152
- iaBookActions.autoRenew = true;
153
- iaBookActions.resetTimerCountdown = true;
154
- lendingStatus1.secondsLeftOnLoan = secondsLeftOnLoan;
155
- } else {
156
- iaBookActions.autoRenew = false;
157
- }
158
-
159
- let lending = {...defaultLendingStatus, ...lendingStatus1};
160
- iaBookActions.lendingStatus = lending;
161
- });
162
- });
163
-
164
148
  document.querySelectorAll('.errorEnable input[type=checkbox]').forEach((item) => {
165
149
  if (window.location.href.indexOf('?error=true') !== -1) {
166
150
  item.checked = true;
@@ -226,10 +210,10 @@
226
210
  await localCache.set({
227
211
  key: `${identifier}-loanTime`,
228
212
  value: new Date(), // current time
229
- ttl: parseInt(iaBookActions.autoRenewTimeConfig.totalTime),
230
- })
213
+ ttl: Number(iaBookActions.loanRenewConfig.totalTime),
214
+ });
231
215
 
232
- await localCache.delete(`${identifier}-pageFlipTime`);
216
+ await localCache.delete(`${identifier}-pageChangedTime`);
233
217
  } catch (error) {}
234
218
 
235
219
  } else if(e.target.value === 'browsingExpired') {
@@ -292,6 +276,17 @@
292
276
  iaBookActions.bwbPurchaseUrl = bwbPurchaseUrl;
293
277
  })
294
278
  });
279
+
280
+ document.querySelector('.pageChangedEvent').addEventListener('click', () => {
281
+ // dispatch event to say book page has flipped
282
+ document.querySelector('ia-book-actions').dispatchEvent(
283
+ new CustomEvent('BookReader:pageChanged', {
284
+ detail: { isPageChanged: true },
285
+ bubbles: true,
286
+ composed: true,
287
+ })
288
+ );
289
+ })
295
290
  </script>
296
291
 
297
292
  <div>
@@ -302,6 +297,9 @@
302
297
  <td class="titleBar">
303
298
  <label><input type="checkbox" name="title" value="title" />Title Bar</label>
304
299
  </td>
300
+ <td>
301
+ <button class="pageChangedEvent">Page Change Event</button>
302
+ </td>
305
303
  </tr>
306
304
  </table>
307
305
  </fieldset>
@@ -311,7 +309,6 @@
311
309
  <table class="initial">
312
310
  <tr colspan='5'>
313
311
  <td class="errorEnable"><label><input type="checkbox" name="errorEnable" value="errorEnable" />Do you want to see error response?</label></td>
314
- <td class="autoRenew"><label><input type="checkbox" name="autoRenew" value="autoRenew" />Do you want to auto-renew 1 hour</label></td>
315
312
  </tr>
316
313
  <tr>
317
314
  <td class="userState">
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@internetarchive/ia-book-actions",
3
- "version": "0.0.29-alpha2",
3
+ "version": "0.0.29-alpha20",
4
4
  "description": "Bookreader actions for borrowable book",
5
5
  "author": "Internet Archive",
6
6
  "license": "AGPL-3.0-only",
@@ -11,8 +11,8 @@
11
11
  },
12
12
  "scripts": {
13
13
  "start": "web-dev-server",
14
- "lint": "eslint --ext .js",
15
- "format": "eslint --ext .js,",
14
+ "lint": "eslint --ext .js,.html . --ignore-path .gitignore && prettier \"**/*.js\" --check --ignore-path .gitignore",
15
+ "format": "eslint --ext .js,.html . --fix --ignore-path .gitignore && prettier \"**/*.js\" --write --ignore-path .gitignore",
16
16
  "test": "yarn run lint && yarn test:fast",
17
17
  "test:fast": "web-test-runner --coverage --playwright --browsers chromium firefox webkit",
18
18
  "test:watch": "web-test-runner --watch --playwright --browsers chromium firefox webkit"
@@ -22,6 +22,7 @@
22
22
  "@internetarchive/local-cache": "^0.2.0",
23
23
  "@internetarchive/modal-manager": "^0.2.1",
24
24
  "@internetarchive/shared-resize-observer": "^0.2.0",
25
+ "@internetarchive/toast-manager": "0.0.1-alpha5",
25
26
  "lit": "^2.2.1"
26
27
  },
27
28
  "devDependencies": {
@@ -43,6 +44,12 @@
43
44
  "sinon": "^13.0.2"
44
45
  },
45
46
  "eslintConfig": {
47
+ "rules": {
48
+ "no-console": "off",
49
+ "no-unused-expressions": "off",
50
+ "class-methods-use-this": "off",
51
+ "lit-a11y/click-events-have-key-events": "off"
52
+ },
46
53
  "extends": [
47
54
  "@open-wc/eslint-config",
48
55
  "eslint-config-prettier"
@@ -53,8 +60,7 @@
53
60
  "arrowParens": "avoid"
54
61
  },
55
62
  "husky": {
56
- "hooks": {
57
- }
63
+ "hooks": {}
58
64
  },
59
65
  "lint-staged": {
60
66
  "*.js": [
@@ -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,
@@ -9,6 +9,10 @@ import CollapsibleActionGroupStyle from '../assets/styles/collapsible-action-gro
9
9
 
10
10
  import { tabletContainerWidth } from '../core/config/constants.js';
11
11
  import { purchaseIcon } from '../assets/data/purchase.js';
12
+ import {
13
+ analyticsCategories,
14
+ analyticsActions,
15
+ } from '../core/config/analytics-event-and-category.js';
12
16
  import {
13
17
  dropdownOpened,
14
18
  dropdownClosed,
@@ -28,8 +32,7 @@ export class CollapsibleActionGroup extends ActionsHandler {
28
32
  dropdownArrow: { type: String },
29
33
  disabled: { type: Boolean },
30
34
  returnUrl: { type: String },
31
- browseAgainNow: { type: Boolean },
32
- autoRenewTimeConfig: { type: Object },
35
+ renewNow: { type: Boolean },
33
36
  };
34
37
  }
35
38
 
@@ -49,8 +52,7 @@ export class CollapsibleActionGroup extends ActionsHandler {
49
52
  this.loaderIcon = 'https://archive.org/upload/images/tree/loading.gif';
50
53
  this.disabled = false;
51
54
  this.returnUrl = '';
52
- this.browseAgainNow = false;
53
- this.autoRenewTimeConfig = {};
55
+ this.renewNow = false;
54
56
  }
55
57
 
56
58
  updated(changed) {
@@ -61,17 +63,28 @@ export class CollapsibleActionGroup extends ActionsHandler {
61
63
  this.resetActions();
62
64
  }
63
65
 
64
- if (this.browseAgainNow === true) {
65
- this.dispatchEvent(
66
- new CustomEvent('autoBrowseBook', {
67
- detail: {
68
- event: { 'autoRenewed1Hour': 'browseBook' },
69
- },
70
- })
71
- );
66
+ if (changed.has('renewNow') && this.renewNow === true) {
67
+ this.dispatchLoanRenewEvent();
72
68
  }
73
69
  }
74
70
 
71
+ // dispatch this event to loan renew handler
72
+ dispatchLoanRenewEvent() {
73
+ const { category, action } = {
74
+ category: analyticsCategories.browse,
75
+ action: analyticsActions.browseLoanRenew,
76
+ };
77
+
78
+ // listen in action-handler.js to execute ajax call on petabox.
79
+ this.dispatchEvent(
80
+ new CustomEvent('loanRenew', {
81
+ detail: {
82
+ event: { category, action },
83
+ },
84
+ })
85
+ );
86
+ }
87
+
75
88
  /**
76
89
  * merge primaryActions and secondaryActions into dropdown
77
90
  */
@@ -5,44 +5,39 @@ export default class TimerCountdown extends LitElement {
5
5
  return {
6
6
  time: { type: Number },
7
7
  autoCheckAt: { type: Number },
8
- resetTimerCountdown: { type: Boolean },
9
8
  };
10
9
  }
11
10
 
12
11
  constructor() {
13
12
  super();
14
- this.time = 60;
13
+ this.time = 0;
15
14
  this.autoCheckAt = 0;
15
+
16
+ // private props
16
17
  this.timerInterval = undefined;
17
- this.resetTimerCountdown = false;
18
18
  }
19
19
 
20
20
  disconnectedCallback() {
21
- clearInterval(this.timerInterval)
22
- }
23
-
24
- firstUpdated() {
25
- this.timerCountdown();
21
+ clearInterval(this.timerInterval);
26
22
  }
27
23
 
28
24
  updated(changed) {
29
- if (changed.has('resetTimerCountdown')) {
25
+ if (changed.has('time') && this.time > 0) {
30
26
  clearInterval(this.timerInterval);
31
- this.timerCountdown()
27
+ this.timerCountdown();
32
28
  }
33
29
  }
34
-
30
+
35
31
  timerCountdown() {
36
32
  this.timerInterval = setInterval(() => {
37
33
  this.time -= 1;
38
34
 
39
35
  // execute 50th minute check
40
- if (this.time === this.autoCheckAt) {
36
+ if (Math.round(this.time) === this.autoCheckAt) {
41
37
  this.dispatchEvent(
42
- new CustomEvent('autoRenewAttempt', {
38
+ new CustomEvent('IABookActions:loanRenew', {
43
39
  detail: {
44
- remainingT: this.time,
45
- bookPageChange: false,
40
+ isPageChanged: false,
46
41
  },
47
42
  bubbles: true,
48
43
  composed: true,
@@ -51,18 +46,16 @@ export default class TimerCountdown extends LitElement {
51
46
  }
52
47
 
53
48
  if (this.time === 0) clearInterval(this.timerInterval);
54
-
55
49
  }, 1000);
56
50
  }
57
51
 
58
-
59
52
  render() {
60
53
  return html`<div id="countdown">
61
- <div id="countdown-number">${this.time}</div>
62
- <svg>
63
- <circle r="18" cx="20" cy="20"></circle>
64
- </svg>
65
- </div>`
54
+ <div id="countdown-number">${Math.round(this.time)}</div>
55
+ <svg>
56
+ <circle r="18" cx="20" cy="20"></circle>
57
+ </svg>
58
+ </div>`;
66
59
  }
67
60
 
68
61
  static get styles() {
@@ -72,22 +65,14 @@ export default class TimerCountdown extends LitElement {
72
65
  font-size: 14px;
73
66
  position: absolute;
74
67
  }
75
- #countdown1 {
76
- position: relative;
77
- margin: auto;
78
- margin-top: 100px;
79
- height: 40px;
80
- width: 40px;
81
- text-align: center;
82
- }
83
-
68
+
84
69
  #countdown-number {
85
70
  color: white;
86
71
  display: inline-block;
87
72
  line-height: 40px;
88
73
  margin-right: 12px;
89
74
  }
90
-
75
+
91
76
  svg {
92
77
  position: absolute;
93
78
  top: 0;
@@ -96,7 +81,7 @@ export default class TimerCountdown extends LitElement {
96
81
  height: 40px;
97
82
  transform: rotateY(-180deg) rotateZ(-90deg);
98
83
  }
99
-
84
+
100
85
  svg circle {
101
86
  stroke-dasharray: 113px;
102
87
  stroke-dashoffset: 0px;
@@ -106,7 +91,7 @@ export default class TimerCountdown extends LitElement {
106
91
  fill: none;
107
92
  animation: countdown 60s linear infinite forwards;
108
93
  }
109
-
94
+
110
95
  @keyframes countdown {
111
96
  from {
112
97
  stroke-dashoffset: 0px;
@@ -115,6 +100,7 @@ export default class TimerCountdown extends LitElement {
115
100
  stroke-dashoffset: 113px;
116
101
  }
117
102
  }
103
+
118
104
  .hidden {
119
105
  display: none;
120
106
  }
@@ -13,6 +13,7 @@ export const analyticsCategories = {
13
13
  export const analyticsActions = {
14
14
  browse: 'Borrow-1Hour',
15
15
  browseAgain: 'Borrow-Again',
16
+ browseLoanRenew: 'Borrow-Loan-Renew',
16
17
  borrow: 'Borrow-14Days',
17
18
  waitlistJoin: 'JoinWaitlist',
18
19
  waitlistLeave: 'LeaveWaitlist',
@@ -1,6 +1,5 @@
1
1
  import { LitElement } from 'lit';
2
2
 
3
- import { LocalCache } from '@internetarchive/local-cache';
4
3
  import { URLHelper } from '../../config/url-helper.js';
5
4
  import ActionsHandlerService from './actions-handler-service.js';
6
5
  import * as Cookies from '../doc-cookies.js';
@@ -16,14 +15,7 @@ import * as Cookies from '../doc-cookies.js';
16
15
  export default class ActionsHandler extends LitElement {
17
16
  constructor() {
18
17
  super();
19
- this.identifier = '';
20
18
  this.ajaxTimeout = 6000;
21
- this.returnUrl = '';
22
- this.autoRenewTimeConfig = {};
23
-
24
- this.localCache = new LocalCache({
25
- namespace: '1HourLoanStorage',
26
- });
27
19
 
28
20
  this.bindEvents();
29
21
  }
@@ -36,7 +28,7 @@ export default class ActionsHandler extends LitElement {
36
28
  );
37
29
  }
38
30
 
39
- async bindEvents() {
31
+ bindEvents() {
40
32
  this.addEventListener('browseBook', ({ detail }) => {
41
33
  this.handleBrowseIt();
42
34
  this.setConsecutiveLoanCounts();
@@ -51,9 +43,8 @@ export default class ActionsHandler extends LitElement {
51
43
  this.sendEvent(category, action);
52
44
  });
53
45
 
54
- this.addEventListener('autoBrowseBook', ({ detail }) => {
55
- console.log('auto renew handlerExecuted!!');
56
- this.handleAutoBrowseIt();
46
+ this.addEventListener('loanRenew', ({ detail }) => {
47
+ this.handleLoanRenew();
57
48
  const { category, action } = detail.event;
58
49
  this.sendEvent(category, action);
59
50
  });
@@ -112,7 +103,7 @@ export default class ActionsHandler extends LitElement {
112
103
  handleBrowseIt() {
113
104
  const action = 'browse_book';
114
105
  this.dispatchToggleActionGroup();
115
- this.setBrowseTimeSession()
106
+ this.setBrowseTimeSession();
116
107
 
117
108
  ActionsHandlerService({
118
109
  action,
@@ -126,9 +117,9 @@ export default class ActionsHandler extends LitElement {
126
117
  });
127
118
  }
128
119
 
129
- handleAutoBrowseIt() {
130
- const action = 'auto_browse';
131
- this.setBrowseTimeSession()
120
+ handleLoanRenew() {
121
+ const action = 'renew_loan';
122
+ this.setBrowseTimeSession();
132
123
 
133
124
  ActionsHandlerService({
134
125
  action,
@@ -140,7 +131,6 @@ export default class ActionsHandler extends LitElement {
140
131
  });
141
132
  }
142
133
 
143
-
144
134
  handleReturnIt() {
145
135
  const action = 'return_loan';
146
136
  this.dispatchToggleActionGroup();
@@ -291,6 +281,24 @@ export default class ActionsHandler extends LitElement {
291
281
  }
292
282
  }
293
283
 
284
+ /**
285
+ * set browse time in indexedDB
286
+ */
287
+ async setBrowseTimeSession() {
288
+ try {
289
+ await this.localCache.set({
290
+ key: `${this.identifier}-loanTime`,
291
+ value: new Date(), // current time
292
+ ttl: Number(this.loanRenewConfig.totalTime),
293
+ });
294
+
295
+ // delete flip time
296
+ await this.localCache.delete(`${this.identifier}-pageChangedTime`);
297
+ } catch (error) {
298
+ console.log(error);
299
+ }
300
+ }
301
+
294
302
  deleteLoanCookies() {
295
303
  const date = new Date();
296
304
  date.setTime(date.getTime() - 24 * 60 * 60 * 1000); // one day ago
@@ -310,17 +318,4 @@ export default class ActionsHandler extends LitElement {
310
318
  '.archive.org'
311
319
  );
312
320
  }
313
-
314
- async setBrowseTimeSession() {
315
- try {
316
- // set a value
317
- await this.localCache.set({
318
- key: `${this.identifier}-loanTime`,
319
- value: new Date(), // current time
320
- ttl: parseInt(this.autoRenewTimeConfig.totalTime),
321
- })
322
-
323
- await this.localCache.delete(`${this.identifier}-pageFlipTime`);
324
- } catch (error) {}
325
- }
326
321
  }
@@ -0,0 +1,148 @@
1
+ import { nothing } from 'lit';
2
+
3
+ /**
4
+ * This class is used to determine if use is eligible for auto renew loan.
5
+ *
6
+ */
7
+ export class OneHourLoanRenew {
8
+ constructor(isPageChanged, identifier, localCache, loanRenewConfig) {
9
+ this.isPageChanged = isPageChanged;
10
+ this.identifier = identifier;
11
+ this.localCache = localCache;
12
+ this.loanRenewConfig = loanRenewConfig;
13
+
14
+ // messages for auto return machenism
15
+ this.loanRenewMessage = 'This book has been renewed for another hour.';
16
+ this.loanReturnMessage =
17
+ 'This book has been auto-returned due to inactivity.';
18
+ this.loanReturnWarning =
19
+ 'This book will be auto-returned in 10 minutes unless you turn a page.';
20
+
21
+ // private props
22
+ this.loanRenewResult = {
23
+ texts: null,
24
+ renewNow: false,
25
+ };
26
+
27
+ return this.handleLoanRenew();
28
+ }
29
+
30
+ handleLoanRenew() {
31
+ try {
32
+ // when user click/flip on book page
33
+ if (this.isPageChanged) {
34
+ console.log('page changed!!');
35
+ return this.pageChanged(); // user clicked on page
36
+ }
37
+
38
+ return this.autoChecker(); // auto checker at 50th minute
39
+ } catch (error) {
40
+ console.log(error);
41
+ }
42
+
43
+ return nothing;
44
+ }
45
+
46
+ async pageChanged() {
47
+ const {
48
+ totalTime, // 60
49
+ autoCheckAt, // 50
50
+ } = this.loanRenewConfig;
51
+ const currentTime = new Date();
52
+ const loanTime = await this.localCache.get(`${this.identifier}-loanTime`);
53
+
54
+ const lastTimeFrame = this.changeTime(
55
+ loanTime,
56
+ totalTime - autoCheckAt,
57
+ 'add'
58
+ );
59
+
60
+ // if user viewed new page in last 10 minutes, renew immediately
61
+ if (currentTime >= lastTimeFrame) {
62
+ console.log('Yes, borrow it again now!');
63
+ this.loanRenewResult.texts = this.loanRenewMessage;
64
+ this.loanRenewResult.renewNow = true;
65
+
66
+ this.setLoanRenewedTime();
67
+ }
68
+
69
+ this.setPageChangedTime();
70
+ return this.loanRenewResult;
71
+ }
72
+
73
+ async autoChecker() {
74
+ const currentTime = new Date();
75
+ const { pageChangedInLast } = this.loanRenewConfig;
76
+ const lastPageFlipTime = await this.localCache.get(
77
+ `${this.identifier}-pageChangedTime`
78
+ );
79
+
80
+ // last 15 min if used flipped a page.
81
+ const lastFlipTimeFrame = this.changeTime(
82
+ currentTime,
83
+ pageChangedInLast,
84
+ 'sub'
85
+ ); // 15 seconds
86
+
87
+ if (
88
+ lastPageFlipTime === undefined ||
89
+ lastPageFlipTime <= lastFlipTimeFrame
90
+ ) {
91
+ // not viewed
92
+ console.log("Not viewed, don't borrow it!");
93
+ this.loanRenewResult.texts = this.loanReturnWarning;
94
+ this.loanRenewResult.renewNow = false;
95
+ } else if (lastPageFlipTime >= lastFlipTimeFrame) {
96
+ // viewed in last time frame
97
+ console.log('Yes viewed, borrow it again!');
98
+ this.loanRenewResult.texts = this.loanRenewMessage;
99
+ this.loanRenewResult.renewNow = true;
100
+
101
+ this.setLoanRenewedTime();
102
+ }
103
+
104
+ return this.loanRenewResult;
105
+ }
106
+
107
+ // save page changed time in indexedDB
108
+ async setPageChangedTime() {
109
+ await this.localCache.set({
110
+ key: `${this.identifier}-pageChangedTime`,
111
+ value: new Date(), // current time
112
+ ttl: Number(this.loanRenewConfig.totalTime),
113
+ });
114
+ }
115
+
116
+ async setLoanRenewedTime() {
117
+ try {
118
+ // last 15 min if used flipped a page.
119
+ const expireDate = this.changeTime(
120
+ new Date(),
121
+ this.loanRenewConfig.totalTime,
122
+ 'add'
123
+ ); // 15 seconds
124
+
125
+ // set a value
126
+ await this.localCache.set({
127
+ key: `${this.identifier}-loanTime`,
128
+ value: expireDate, // expireDate time
129
+ ttl: Number(this.loanRenewConfig.totalTime),
130
+ });
131
+
132
+ // delete pageChangedTime when book is auto renew at nth minute
133
+ await this.localCache.delete(`${this.identifier}-pageChangedTime`);
134
+ } catch (error) {
135
+ console.log(error);
136
+ }
137
+ }
138
+
139
+ changeTime(date, minutes, op) {
140
+ if (date === undefined) return nothing;
141
+
142
+ if (op === 'sub') {
143
+ return new Date(date.getTime() - minutes * 1000);
144
+ }
145
+
146
+ return new Date(date.getTime() + minutes * 1000);
147
+ }
148
+ }
@@ -3,6 +3,8 @@ 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';
6
+ import '@internetarchive/toast-manager';
7
+ import { ToastConfig } from '@internetarchive/toast-manager';
6
8
  import { LocalCache } from '@internetarchive/local-cache';
7
9
 
8
10
  import './components/collapsible-action-group.js';
@@ -10,12 +12,11 @@ import './components/book-title-bar.js';
10
12
  import './components/text-group.js';
11
13
  import './components/info-icon.js';
12
14
  import './components/timer-countdown.js';
13
- import './components/toast-message.js';
14
15
 
15
- import { GetLendingActions } from './core/services/get-lending-actions.js';
16
16
  import { mobileContainerWidth } from './core/config/constants.js';
17
+ import { GetLendingActions } from './core/services/get-lending-actions.js';
17
18
  import { LoanTokenPoller } from './core/services/loan-token-poller.js';
18
- import { OneHourAutoRenew } from './core/services/one-hour-auto-renew.js';
19
+ import { OneHourLoanRenew } from './core/services/one-hour-loan-renew.js';
19
20
 
20
21
  export const events = {
21
22
  browseExpired: 'IABookReader:BrowsingHasExpired',
@@ -36,17 +37,13 @@ export default class IABookActions extends LitElement {
36
37
  attribute: false,
37
38
  },
38
39
  barType: { type: String },
39
- autoRenew: { type: Boolean },
40
40
  sharedObserver: { attribute: false },
41
41
  disableActionGroup: { type: Boolean },
42
42
  modal: { Object },
43
43
  tokenDelay: { type: Number },
44
- toastTexts: { type: String },
45
- remainingTime: { type: Number },
46
- resetTimerCountdown: { type: Boolean },
47
44
  localCache: { type: Object },
48
- autoRenewTimeConfig: { type: Object },
49
- browseAgainNow: { type: Boolean },
45
+ loanRenewConfig: { type: Object },
46
+ loanRenewResult: { type: Object },
50
47
  };
51
48
  }
52
49
 
@@ -61,15 +58,21 @@ export default class IABookActions extends LitElement {
61
58
  this.bwbPurchaseUrl = '';
62
59
  this.lendingBarPostInit = () => {};
63
60
  this.barType = 'action'; // 'title'|'action'
64
- this.autoRenew = false;
65
61
  this.sharedObserver = undefined;
66
62
  this.disableActionGroup = false;
67
63
  this.modal = undefined;
68
64
  this.tokenDelay = 120000; // 2 minutes
69
65
  this.localCache = new LocalCache({
70
- namespace: '1HourLoanStorage'
71
- }),
72
- this.autoRenewTimeConfig = {},
66
+ namespace: 'loanRenew',
67
+ });
68
+
69
+ // loan auto renew
70
+ this.isPageChanged = false;
71
+ this.loanRenewConfig = {};
72
+ this.loanRenewResult = {
73
+ texts: null,
74
+ renewNow: false,
75
+ };
73
76
 
74
77
  // private props
75
78
  this.primaryActions = [];
@@ -79,12 +82,10 @@ export default class IABookActions extends LitElement {
79
82
  this.lendingOptions = {};
80
83
  this.borrowType = ''; // (browsed|borrowed)
81
84
  this.consecutiveLoanCounts = 1; // consecutive loan count
82
- this.browseAgainNow = false;
83
85
  this.browserTimer = undefined;
84
- this.hideToast = true;
85
86
 
86
- this.bindAutoRenewEvent();
87
- this.resetTimerCountdown = false;
87
+ this._shadowRoot = this.attachShadow({ mode: "open" });
88
+ this.bindLoanRenewEvents();
88
89
  }
89
90
 
90
91
  disconnectedCallback() {
@@ -106,35 +107,66 @@ export default class IABookActions extends LitElement {
106
107
  this.update();
107
108
  }
108
109
 
109
- if (changed.has('autoRenew')) {
110
- this.resetTimerCountdown = true;
111
- }
112
110
  if (changed.has('sharedObserver')) {
113
111
  this.disconnectResizeObserver();
114
112
  this.setupResizeObserver();
115
113
  }
116
114
  }
117
115
 
118
- bindAutoRenewEvent() {
119
- // dispatched this event from timer-countdown
120
- this.addEventListener('autoRenewAttempt', async event => {
121
- const isPageFlipped = event.detail.bookPageChange;
122
-
123
- const dd = await new OneHourAutoRenew(
124
- isPageFlipped,
125
- this.identifier,
126
- this.localCache,
127
- this.autoRenewTimeConfig,
128
- this.lendingStatus.user_has_browsed && !this.lendingStatus.browsingExpired
129
- );
130
-
131
- this.toastTexts = dd.toastTexts;
132
- this.hideToast = dd.hideToast;
133
- this.browseAgainNow = dd.renewNow;
134
- if (this.browseAgainNow) {
135
- await this.browseHasRenew();
116
+ bindLoanRenewEvents() {
117
+ // dispatched this event from bookreader page changed
118
+ window.addEventListener('BookReader:pageChanged', () => {
119
+ // early return if book is not browsed (1 hour borrow)
120
+ if (this.borrowType === 'browsed') {
121
+ this.isPageChanged = true;
122
+ this.executeLoanRenew();
136
123
  }
137
- // this.handleBookAutoRenew(event);
124
+
125
+ return nothing;
126
+ });
127
+
128
+ // dispatched this event from timer-countdown
129
+ this.addEventListener('IABookActions:loanRenew', () => {
130
+ this.isPageChanged = false;
131
+ this.executeLoanRenew();
132
+ });
133
+ }
134
+
135
+ async executeLoanRenew() {
136
+ this.loanRenewResult = await new OneHourLoanRenew(
137
+ this.isPageChanged,
138
+ this.identifier,
139
+ this.localCache,
140
+ this.loanRenewConfig
141
+ );
142
+
143
+ console.log(this.loanRenewResult);
144
+
145
+ this.showToastMessage();
146
+
147
+ if (this.loanRenewResult.renewNow) {
148
+ this.tokenPoller.disconnectedInterval();
149
+ await this.browseHasRenew();
150
+ }
151
+
152
+ return nothing;
153
+ }
154
+
155
+ async showToastMessage() {
156
+ let toastTemplate = this.shadowRoot.querySelector('toast-template');
157
+ if (!toastTemplate) {
158
+ toastTemplate = document.createElement('toast-template');
159
+ }
160
+
161
+ await this.shadowRoot.appendChild(toastTemplate)
162
+ // await document.body.appendChild(toastTemplate);
163
+
164
+ const config = new ToastConfig();
165
+ config.texts = this.loanRenewResult?.texts;
166
+ config.dismisOnClick = true;
167
+
168
+ toastTemplate.showToast({
169
+ config
138
170
  });
139
171
  }
140
172
 
@@ -144,18 +176,25 @@ export default class IABookActions extends LitElement {
144
176
 
145
177
  // remove respected key:value
146
178
  await this.localCache.delete(`${this.identifier}-loanTime`);
147
- await this.localCache.delete(`${this.identifier}-pageFlipTime`);
179
+ await this.localCache.delete(`${this.identifier}-pageChangedTime`);
148
180
 
149
181
  // show message after browsed book is expired.
150
- this.toastTexts = this.autoReturnMessage;
182
+ this.loanRenewResult.renewNow = false;
183
+ this.loanRenewResult.texts =
184
+ 'This book has been auto-returned due to inactivity.';
185
+
186
+ this.showToastMessage();
151
187
  }
152
188
 
153
189
  async browseHasRenew() {
154
- const currStatus = {
190
+ const loanTime = await this.localCache.get(`${this.identifier}-loanTime`);
191
+ const secondsLeft = (loanTime - new Date()) / 1000; // different in seconds
192
+
193
+ const currStatus = {
155
194
  ...this.lendingStatus,
156
195
  user_has_browsed: true,
157
196
  browsingExpired: false,
158
- secondsLeftOnLoan: 61
197
+ secondsLeftOnLoan: secondsLeft,
159
198
  };
160
199
  this.lendingStatus = currStatus;
161
200
 
@@ -174,7 +213,6 @@ export default class IABookActions extends LitElement {
174
213
  return;
175
214
  }
176
215
 
177
- // expired book and show browse again button
178
216
  const timeLeft = secondsLeftOnLoan * 1000;
179
217
  this.browserTimer = setTimeout(() => {
180
218
  this.browseHasExpired();
@@ -234,13 +272,16 @@ export default class IABookActions extends LitElement {
234
272
  });
235
273
 
236
274
  this.borrowType = actions.borrowType;
237
- this.remainingTime = this.lendingStatus.secondsLeftOnLoan;
238
275
 
239
276
  const hasExpired =
240
277
  'browsingExpired' in this.lendingStatus &&
241
278
  this.lendingStatus?.browsingExpired;
242
279
  if (hasExpired) {
243
- window?.Sentry?.captureMessage('setupLendingToolbarActions hasExpired');
280
+ if (!this.tokenPoller.disconnectedInterval) {
281
+ window?.Sentry?.captureMessage(
282
+ 'setupLendingToolbarActions hasExpired - no tokenPoller'
283
+ );
284
+ }
244
285
  this.tokenPoller?.disconnectedInterval();
245
286
  /** Global event - always fire */
246
287
  this.dispatchEvent(
@@ -265,7 +306,16 @@ export default class IABookActions extends LitElement {
265
306
  return;
266
307
  }
267
308
 
268
- this.startLoanTokenPoller();
309
+ // if (book is about to renew)
310
+ // - start token to poller around this.tokenDelay time until renew is completed
311
+ // else
312
+ // - start token poller immediately
313
+ setTimeout(
314
+ () => {
315
+ this.startLoanTokenPoller();
316
+ },
317
+ this.loanRenewResult.renewNow ? this.tokenDelay : 100
318
+ );
269
319
  }
270
320
 
271
321
  render() {
@@ -290,26 +340,14 @@ export default class IABookActions extends LitElement {
290
340
  get timerCountdownTemplate() {
291
341
  return this.borrowType === 'browsed'
292
342
  ? html`<timer-countdown
293
- time=${Number(this.remainingTime)}
294
- .autoCheckAt=${this.autoRenewTimeConfig.autoCheckAt}
295
- ?resetTimerCountdown=${this.resetTimerCountdown}
343
+ time=${Number(this.lendingStatus.secondsLeftOnLoan)}
344
+ .autoCheckAt=${this.loanRenewConfig.autoCheckAt}
296
345
  ></timer-countdown>`
297
346
  : nothing;
298
347
  }
299
348
 
300
- get toastMessageTemplate() {
301
- return html`
302
- <toast-message
303
- .texts=${this.toastTexts}
304
- .dismisOnClick="1"
305
- ?hideToast=${this.hideToast}
306
- ></toast-message>
307
- `;
308
- }
309
-
310
349
  get bookActionBar() {
311
350
  return html`
312
- <button @click=${() => this.pageFlipEvent()}>Page Flip</button>
313
351
  <collapsible-action-group
314
352
  .userid=${this.userid}
315
353
  .identifier=${this.identifier}
@@ -319,32 +357,20 @@ export default class IABookActions extends LitElement {
319
357
  .width=${this.width}
320
358
  .borrowType=${this.borrowType}
321
359
  .returnUrl=${this.returnUrl}
322
- .autoRenew=${this.autoRenew}
323
- .autoRenewTimeConfig=${this.autoRenewTimeConfig}
360
+ .localCache=${this.localCache}
361
+ .loanRenewConfig=${this.loanRenewConfig}
324
362
  ?hasAdminAccess=${this.hasAdminAccess}
325
363
  ?disabled=${this.disableActionGroup}
326
- ?browseAgainNow=${this.browseAgainNow}
364
+ ?renewNow=${this.loanRenewResult.renewNow}
327
365
  @lendingActionError=${this.handleLendingActionError}
328
366
  @toggleActionGroup=${this.handleToggleActionGroup}
329
367
  >
330
368
  </collapsible-action-group>
331
369
  ${this.textGroupTemplate} ${this.infoIconTemplate}
332
- ${this.timerCountdownTemplate} ${this.toastMessageTemplate}
370
+ ${this.timerCountdownTemplate}
333
371
  `;
334
372
  }
335
373
 
336
- // will move this function to bookreader...
337
- async pageFlipEvent() {
338
- try {
339
- // dispatch event to say book page has flipped
340
- document.querySelector("ia-book-actions").dispatchEvent(
341
- new CustomEvent("autoRenewAttempt", {
342
- detail: { bookPageChange: true },
343
- })
344
- );
345
- } catch (error) { console.log(error) }
346
- }
347
-
348
374
  /**
349
375
  * enable access of borrowed/browsed books
350
376
  */
@@ -485,7 +511,7 @@ export default class IABookActions extends LitElement {
485
511
  get textGroupTemplate() {
486
512
  return html`<text-group
487
513
  textClass=${this.textClass}
488
- texts=${this.primaryTitle}
514
+ .texts=${this.primaryTitle}
489
515
  >
490
516
  </text-group>`;
491
517
  }
@@ -498,20 +524,23 @@ export default class IABookActions extends LitElement {
498
524
  return css`
499
525
  :host {
500
526
  display: block;
501
- background: var(--primaryBGColor, #000);
502
- color: var(--primaryTextColor, #fff);
503
527
  }
528
+
504
529
  .lending-wrapper {
505
530
  width: 100%;
506
531
  margin: 0 auto;
532
+ background: var(--primaryBGColor, #000);
533
+ color: var(--primaryTextColor, #fff);
507
534
  display: inline-flex;
508
535
  align-items: center;
509
536
  justify-content: center;
510
537
  flex-wrap: wrap;
511
538
  }
512
539
 
513
- toast-message {
514
- --messageHeightFromTop: 18%;
540
+ toast-template {
541
+ --toastTopMargin: 80px;
542
+ --toastBGColor: #333;
543
+ --toastFontColor: #fff;
515
544
  }
516
545
  `;
517
546
  }
@@ -1,58 +0,0 @@
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
- `;
@@ -1,60 +0,0 @@
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,148 +0,0 @@
1
- import ActionsHandlerService from './actions-handler/actions-handler-service.js';
2
-
3
- /**
4
- * This class is used to determine if use is eligible for auto renew loan.
5
- *
6
- * ActionsHandlerService is a function being used to execute
7
- */
8
- export class OneHourAutoRenew {
9
- constructor(pageClick, identifier, localCache, autoRenewTimeConfig, userHasBrowsed) {
10
- this.pageClick = pageClick;
11
- this.identifier = identifier;
12
- this.localCache = localCache;
13
- this.autoRenewTimeConfig = autoRenewTimeConfig;
14
- this.userHasBrowsed = userHasBrowsed;
15
-
16
- // private props
17
- this.resultRes = {
18
- toastTexts: null,
19
- hideToast: true,
20
- renewNow: false,
21
- };
22
-
23
- // messages for auto return machenism
24
- this.autoRenewMessage = 'This book has been renewed for another hour.';
25
- this.autoReturnMessage = 'This book has been auto-returned due to inactivity.';
26
- this.autoReturnWarning = 'This book will be auto-returned in 10 minutes unless you turn a page.';
27
-
28
- return this.handleBookAutoRenew();
29
- }
30
-
31
- async handleBookAutoRenew() {
32
- try {
33
- // if page is not flipped in last 15 minutes,
34
- // instantaly renew it if user viewed new page in last 10 minutes
35
- if (this.pageClick) {
36
- console.log('page flipped!')
37
- this.setPageFlippedTime();
38
- return this.userFlippedBookPage(); // user clicked on page
39
- } else {
40
- console.log('auto checker!')
41
- return this.autoCheckerTimer(); // auto checker at 50th minute
42
- }
43
-
44
- } catch (error) {
45
- console.log(error)
46
- }
47
- }
48
-
49
- async setPageFlippedTime() {
50
- // set pageFlipTime time in localStorage
51
- await this.localCache.set({
52
- key: `${this.identifier}-pageFlipTime`,
53
- value: new Date(), // current time
54
- ttl: parseInt(this.autoRenewTimeConfig.totalTime),
55
- })
56
- }
57
-
58
- async userFlippedBookPage() {
59
- const {
60
- totalTime, // 60
61
- autoCheckAt, // 50
62
- } = this.autoRenewTimeConfig;
63
- const currentDate = new Date();
64
- const loanTime = await this.localCache.get(`${this.identifier}-loanTime`);
65
-
66
- const lastTimeFrame = await this.changeTime(
67
- loanTime,
68
- totalTime - autoCheckAt,
69
- 'add'
70
- ); // 50 seconds
71
-
72
- if (currentDate >= lastTimeFrame) {
73
- console.log('1 YES NOW BORROW IT AGAIN NOW!');
74
- this.resultRes.toastTexts = this.autoRenewMessage;
75
- this.resultRes.browseAgainNow = true;
76
- this.resultRes.hideToast = false;
77
- this.resultRes.renewNow = true;
78
- }
79
-
80
- return this.resultRes;
81
- }
82
-
83
- async autoCheckerTimer() {
84
- const currentDate = new Date();
85
- const { FlippedInLast } = this.autoRenewTimeConfig;
86
- const lastPageFlipTime = await this.localCache.get(`${this.identifier}-pageFlipTime`);
87
-
88
- // last 15 min if used flipped a page.
89
- const lastFlipTimeFrame = await this.changeTime(
90
- currentDate,
91
- FlippedInLast,
92
- 'sub'
93
- ); // 15 seconds
94
-
95
- if (lastPageFlipTime === undefined || lastPageFlipTime <= lastFlipTimeFrame) { // not viewed
96
- console.log('2');
97
- this.resultRes.toastTexts = this.autoReturnWarning;
98
- this.resultRes.hideToast = false;
99
- } else if (lastPageFlipTime >= lastFlipTimeFrame) { // viewed in last time frame
100
- console.log('3');
101
- this.resultRes.toastTexts = this.autoRenewMessage;
102
- this.resultRes.browseAgainNow = true;
103
- this.resultRes.hideToast = false;
104
- this.resultRes.renewNow = true;
105
- }
106
-
107
- return this.resultRes;
108
- }
109
-
110
- async changeTime(date, minutes, op) {
111
- if (date === undefined) return undefined;
112
-
113
- if (op === 'sub') {
114
- return new Date(date.getTime() - minutes * 1000);
115
- }
116
-
117
- return new Date(date.getTime() + minutes * 1000);
118
- }
119
-
120
-
121
- sendEvent(eventCategory, eventAction) {
122
- window?.archive_analytics?.send_event(
123
- eventCategory,
124
- eventAction,
125
- `identifier=${this.identifier}`
126
- );
127
- }
128
-
129
- async handleAutoRenew() {
130
- const action = 'renew_loan';
131
- ActionsHandlerService({
132
- identifier: this.identifier,
133
- action,
134
- error: data => {
135
- window?.Sentry?.captureMessage(
136
- 'handleAutoRenew error',
137
- JSON.stringify(data)
138
- );
139
- // this.errorCallback({ detail: { action, data } });
140
- // send LendingServiceError to GA
141
- this.sendEvent('handleAutoRenew', action);
142
- },
143
- success: () => {
144
- return 'success';
145
- },
146
- });
147
- }
148
- }