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

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
@@ -87,13 +87,18 @@
87
87
 
88
88
  <script type="module">
89
89
  /* eslint-disable */
90
-
91
90
  import '../src/ia-book-actions.js';
92
91
  import {
93
92
  defaultLendingStatus,
94
93
  canBrowseAndBorrow,
95
94
  } from '../src/assets/data/lending-get-status-service.js';
96
95
  import { ModalConfig } from '@internetarchive/modal-manager';
96
+ import { LocalCache } from '@internetarchive/local-cache';
97
+
98
+ const localCache = new LocalCache({
99
+ namespace: '1HourLoanStorage',
100
+ defaultTTL: 1 * 60, // 1 minute
101
+ });
97
102
 
98
103
  const userid = '@neeraj-archive';
99
104
  const identifier = 'naturalhistoryof00unse_4111';
@@ -121,8 +126,15 @@
121
126
  iaBookActions.lendingBarPostInit = lendingBarPostInit;
122
127
  iaBookActions.tokenDelay = 2000; // 2 seconds
123
128
  iaBookActions.returnUrl = 'https://openlibrary.org';
129
+ 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
134
+ };
124
135
 
125
136
  let lendingStatus1 = iaBookActions.lendingStatus;
137
+ let secondsLeftOnLoan = 60;
126
138
 
127
139
  document.querySelectorAll('.titleBar input[type=checkbox]').forEach((item) => {
128
140
  item.addEventListener('click', (e) => {
@@ -134,6 +146,21 @@
134
146
  });
135
147
  });
136
148
 
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
+
137
164
  document.querySelectorAll('.errorEnable input[type=checkbox]').forEach((item) => {
138
165
  if (window.location.href.indexOf('?error=true') !== -1) {
139
166
  item.checked = true;
@@ -187,12 +214,24 @@
187
214
  });
188
215
 
189
216
  document.querySelectorAll('.availableToBrowse input[type=radio]').forEach((item) => {
190
- item.addEventListener('click', (e) => {
217
+ item.addEventListener('click', async (e) => {
191
218
  if (e.target.value === 'user_has_browsed') {
192
219
  lendingStatus1.user_has_browsed = true;
193
220
  lendingStatus1.available_to_browse = false;
194
- lendingStatus1.secondsLeftOnLoan = 10;
221
+ lendingStatus1.secondsLeftOnLoan = secondsLeftOnLoan;
195
222
  lendingStatus1.browsingExpired = false;
223
+
224
+ try {
225
+ // set a value
226
+ await localCache.set({
227
+ key: `${identifier}-loanTime`,
228
+ value: new Date(), // current time
229
+ ttl: parseInt(iaBookActions.autoRenewTimeConfig.totalTime),
230
+ })
231
+
232
+ await localCache.delete(`${identifier}-pageFlipTime`);
233
+ } catch (error) {}
234
+
196
235
  } else if(e.target.value === 'browsingExpired') {
197
236
  lendingStatus1.user_has_browsed = true;
198
237
  lendingStatus1.available_to_browse = false;
@@ -272,6 +311,7 @@
272
311
  <table class="initial">
273
312
  <tr colspan='5'>
274
313
  <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>
275
315
  </tr>
276
316
  <tr>
277
317
  <td class="userState">
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@internetarchive/ia-book-actions",
3
- "version": "0.0.28",
3
+ "version": "0.0.29-alpha2",
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,.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",
14
+ "lint": "eslint --ext .js",
15
+ "format": "eslint --ext .js,",
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"
@@ -54,7 +54,6 @@
54
54
  },
55
55
  "husky": {
56
56
  "hooks": {
57
- "pre-commit": "lint-staged"
58
57
  }
59
58
  },
60
59
  "lint-staged": {
@@ -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,8 @@ export class CollapsibleActionGroup extends ActionsHandler {
28
28
  dropdownArrow: { type: String },
29
29
  disabled: { type: Boolean },
30
30
  returnUrl: { type: String },
31
+ browseAgainNow: { type: Boolean },
32
+ autoRenewTimeConfig: { type: Object },
31
33
  };
32
34
  }
33
35
 
@@ -47,6 +49,8 @@ export class CollapsibleActionGroup extends ActionsHandler {
47
49
  this.loaderIcon = 'https://archive.org/upload/images/tree/loading.gif';
48
50
  this.disabled = false;
49
51
  this.returnUrl = '';
52
+ this.browseAgainNow = false;
53
+ this.autoRenewTimeConfig = {};
50
54
  }
51
55
 
52
56
  updated(changed) {
@@ -56,6 +60,16 @@ export class CollapsibleActionGroup extends ActionsHandler {
56
60
  ) {
57
61
  this.resetActions();
58
62
  }
63
+
64
+ if (this.browseAgainNow === true) {
65
+ this.dispatchEvent(
66
+ new CustomEvent('autoBrowseBook', {
67
+ detail: {
68
+ event: { 'autoRenewed1Hour': 'browseBook' },
69
+ },
70
+ })
71
+ );
72
+ }
59
73
  }
60
74
 
61
75
  /**
@@ -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,128 @@
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
+ autoCheckAt: { type: Number },
8
+ resetTimerCountdown: { type: Boolean },
9
+ };
10
+ }
11
+
12
+ constructor() {
13
+ super();
14
+ this.time = 60;
15
+ this.autoCheckAt = 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.autoCheckAt) {
41
+ this.dispatchEvent(
42
+ new CustomEvent('autoRenewAttempt', {
43
+ detail: {
44
+ remainingT: this.time,
45
+ bookPageChange: false,
46
+ },
47
+ bubbles: true,
48
+ composed: true,
49
+ })
50
+ );
51
+ }
52
+
53
+ if (this.time === 0) clearInterval(this.timerInterval);
54
+
55
+ }, 1000);
56
+ }
57
+
58
+
59
+ render() {
60
+ 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>`
66
+ }
67
+
68
+ static get styles() {
69
+ return css`
70
+ :host {
71
+ right: 5px;
72
+ font-size: 14px;
73
+ position: absolute;
74
+ }
75
+ #countdown1 {
76
+ position: relative;
77
+ margin: auto;
78
+ margin-top: 100px;
79
+ height: 40px;
80
+ width: 40px;
81
+ text-align: center;
82
+ }
83
+
84
+ #countdown-number {
85
+ color: white;
86
+ display: inline-block;
87
+ line-height: 40px;
88
+ margin-right: 12px;
89
+ }
90
+
91
+ svg {
92
+ position: absolute;
93
+ top: 0;
94
+ right: 0;
95
+ width: 40px;
96
+ height: 40px;
97
+ transform: rotateY(-180deg) rotateZ(-90deg);
98
+ }
99
+
100
+ svg circle {
101
+ stroke-dasharray: 113px;
102
+ stroke-dashoffset: 0px;
103
+ stroke-linecap: round;
104
+ stroke-width: 3px;
105
+ stroke: white;
106
+ fill: none;
107
+ animation: countdown 60s linear infinite forwards;
108
+ }
109
+
110
+ @keyframes countdown {
111
+ from {
112
+ stroke-dashoffset: 0px;
113
+ }
114
+ to {
115
+ stroke-dashoffset: 113px;
116
+ }
117
+ }
118
+ .hidden {
119
+ display: none;
120
+ }
121
+ .visible {
122
+ display: inline-block;
123
+ }
124
+ `;
125
+ }
126
+ }
127
+
128
+ 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
+ this.autoRenewTimeConfig = {};
23
+
24
+ this.localCache = new LocalCache({
25
+ namespace: '1HourLoanStorage',
26
+ });
27
+
28
+ this.bindEvents();
22
29
  }
23
30
 
24
31
  sendEvent(eventCategory, eventAction) {
@@ -29,7 +36,7 @@ export default class ActionsHandler extends LitElement {
29
36
  );
30
37
  }
31
38
 
32
- bindEvents() {
39
+ async bindEvents() {
33
40
  this.addEventListener('browseBook', ({ detail }) => {
34
41
  this.handleBrowseIt();
35
42
  this.setConsecutiveLoanCounts();
@@ -44,6 +51,13 @@ export default class ActionsHandler extends LitElement {
44
51
  this.sendEvent(category, action);
45
52
  });
46
53
 
54
+ this.addEventListener('autoBrowseBook', ({ detail }) => {
55
+ console.log('auto renew handlerExecuted!!');
56
+ this.handleAutoBrowseIt();
57
+ const { category, action } = detail.event;
58
+ this.sendEvent(category, action);
59
+ });
60
+
47
61
  this.addEventListener('returnNow', ({ detail }) => {
48
62
  this.handleReturnIt();
49
63
  const { category, action } = detail.event;
@@ -98,6 +112,7 @@ export default class ActionsHandler extends LitElement {
98
112
  handleBrowseIt() {
99
113
  const action = 'browse_book';
100
114
  this.dispatchToggleActionGroup();
115
+ this.setBrowseTimeSession()
101
116
 
102
117
  ActionsHandlerService({
103
118
  action,
@@ -111,6 +126,21 @@ export default class ActionsHandler extends LitElement {
111
126
  });
112
127
  }
113
128
 
129
+ handleAutoBrowseIt() {
130
+ const action = 'auto_browse';
131
+ this.setBrowseTimeSession()
132
+
133
+ ActionsHandlerService({
134
+ action,
135
+ identifier: this.identifier,
136
+ success: () => {},
137
+ error: data => {
138
+ this.dispatchActionError(action, data);
139
+ },
140
+ });
141
+ }
142
+
143
+
114
144
  handleReturnIt() {
115
145
  const action = 'return_loan';
116
146
  this.dispatchToggleActionGroup();
@@ -280,4 +310,17 @@ export default class ActionsHandler extends LitElement {
280
310
  '.archive.org'
281
311
  );
282
312
  }
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
+ }
283
326
  }
@@ -0,0 +1,148 @@
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
+ }
@@ -1,17 +1,21 @@
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';
6
+ import { LocalCache } from '@internetarchive/local-cache';
6
7
 
7
8
  import './components/collapsible-action-group.js';
8
9
  import './components/book-title-bar.js';
9
10
  import './components/text-group.js';
10
11
  import './components/info-icon.js';
12
+ import './components/timer-countdown.js';
13
+ import './components/toast-message.js';
11
14
 
12
15
  import { GetLendingActions } from './core/services/get-lending-actions.js';
13
16
  import { mobileContainerWidth } from './core/config/constants.js';
14
17
  import { LoanTokenPoller } from './core/services/loan-token-poller.js';
18
+ import { OneHourAutoRenew } from './core/services/one-hour-auto-renew.js';
15
19
 
16
20
  export const events = {
17
21
  browseExpired: 'IABookReader:BrowsingHasExpired',
@@ -32,10 +36,17 @@ export default class IABookActions extends LitElement {
32
36
  attribute: false,
33
37
  },
34
38
  barType: { type: String },
39
+ autoRenew: { type: Boolean },
35
40
  sharedObserver: { attribute: false },
36
41
  disableActionGroup: { type: Boolean },
37
42
  modal: { Object },
38
43
  tokenDelay: { type: Number },
44
+ toastTexts: { type: String },
45
+ remainingTime: { type: Number },
46
+ resetTimerCountdown: { type: Boolean },
47
+ localCache: { type: Object },
48
+ autoRenewTimeConfig: { type: Object },
49
+ browseAgainNow: { type: Boolean },
39
50
  };
40
51
  }
41
52
 
@@ -50,10 +61,15 @@ export default class IABookActions extends LitElement {
50
61
  this.bwbPurchaseUrl = '';
51
62
  this.lendingBarPostInit = () => {};
52
63
  this.barType = 'action'; // 'title'|'action'
64
+ this.autoRenew = false;
53
65
  this.sharedObserver = undefined;
54
66
  this.disableActionGroup = false;
55
67
  this.modal = undefined;
56
68
  this.tokenDelay = 120000; // 2 minutes
69
+ this.localCache = new LocalCache({
70
+ namespace: '1HourLoanStorage'
71
+ }),
72
+ this.autoRenewTimeConfig = {},
57
73
 
58
74
  // private props
59
75
  this.primaryActions = [];
@@ -63,6 +79,12 @@ export default class IABookActions extends LitElement {
63
79
  this.lendingOptions = {};
64
80
  this.borrowType = ''; // (browsed|borrowed)
65
81
  this.consecutiveLoanCounts = 1; // consecutive loan count
82
+ this.browseAgainNow = false;
83
+ this.browserTimer = undefined;
84
+ this.hideToast = true;
85
+
86
+ this.bindAutoRenewEvent();
87
+ this.resetTimerCountdown = false;
66
88
  }
67
89
 
68
90
  disconnectedCallback() {
@@ -84,18 +106,65 @@ export default class IABookActions extends LitElement {
84
106
  this.update();
85
107
  }
86
108
 
109
+ if (changed.has('autoRenew')) {
110
+ this.resetTimerCountdown = true;
111
+ }
87
112
  if (changed.has('sharedObserver')) {
88
113
  this.disconnectResizeObserver();
89
114
  this.setupResizeObserver();
90
115
  }
91
116
  }
92
117
 
93
- browseHasExpired() {
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();
136
+ }
137
+ // this.handleBookAutoRenew(event);
138
+ });
139
+ }
140
+
141
+ async browseHasExpired() {
94
142
  const currStatus = { ...this.lendingStatus, browsingExpired: true };
95
143
  this.lendingStatus = currStatus;
144
+
145
+ // remove respected key:value
146
+ await this.localCache.delete(`${this.identifier}-loanTime`);
147
+ await this.localCache.delete(`${this.identifier}-pageFlipTime`);
148
+
149
+ // show message after browsed book is expired.
150
+ this.toastTexts = this.autoReturnMessage;
151
+ }
152
+
153
+ async browseHasRenew() {
154
+ const currStatus = {
155
+ ...this.lendingStatus,
156
+ user_has_browsed: true,
157
+ browsingExpired: false,
158
+ secondsLeftOnLoan: 61
159
+ };
160
+ this.lendingStatus = currStatus;
161
+
162
+ this.startBrowseTimer();
96
163
  }
97
164
 
98
165
  startBrowseTimer() {
166
+ clearTimeout(this.browserTimer);
167
+
99
168
  const {
100
169
  browsingExpired,
101
170
  user_has_browsed,
@@ -105,8 +174,9 @@ export default class IABookActions extends LitElement {
105
174
  return;
106
175
  }
107
176
 
177
+ // expired book and show browse again button
108
178
  const timeLeft = secondsLeftOnLoan * 1000;
109
- setTimeout(() => {
179
+ this.browserTimer = setTimeout(() => {
110
180
  this.browseHasExpired();
111
181
  }, timeLeft);
112
182
  }
@@ -164,6 +234,7 @@ export default class IABookActions extends LitElement {
164
234
  });
165
235
 
166
236
  this.borrowType = actions.borrowType;
237
+ this.remainingTime = this.lendingStatus.secondsLeftOnLoan;
167
238
 
168
239
  const hasExpired =
169
240
  'browsingExpired' in this.lendingStatus &&
@@ -216,8 +287,29 @@ export default class IABookActions extends LitElement {
216
287
  ></book-title-bar>`;
217
288
  }
218
289
 
290
+ get timerCountdownTemplate() {
291
+ return this.borrowType === 'browsed'
292
+ ? html`<timer-countdown
293
+ time=${Number(this.remainingTime)}
294
+ .autoCheckAt=${this.autoRenewTimeConfig.autoCheckAt}
295
+ ?resetTimerCountdown=${this.resetTimerCountdown}
296
+ ></timer-countdown>`
297
+ : nothing;
298
+ }
299
+
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
+
219
310
  get bookActionBar() {
220
311
  return html`
312
+ <button @click=${() => this.pageFlipEvent()}>Page Flip</button>
221
313
  <collapsible-action-group
222
314
  .userid=${this.userid}
223
315
  .identifier=${this.identifier}
@@ -227,16 +319,32 @@ export default class IABookActions extends LitElement {
227
319
  .width=${this.width}
228
320
  .borrowType=${this.borrowType}
229
321
  .returnUrl=${this.returnUrl}
322
+ .autoRenew=${this.autoRenew}
323
+ .autoRenewTimeConfig=${this.autoRenewTimeConfig}
230
324
  ?hasAdminAccess=${this.hasAdminAccess}
231
325
  ?disabled=${this.disableActionGroup}
326
+ ?browseAgainNow=${this.browseAgainNow}
232
327
  @lendingActionError=${this.handleLendingActionError}
233
328
  @toggleActionGroup=${this.handleToggleActionGroup}
234
329
  >
235
330
  </collapsible-action-group>
236
331
  ${this.textGroupTemplate} ${this.infoIconTemplate}
332
+ ${this.timerCountdownTemplate} ${this.toastMessageTemplate}
237
333
  `;
238
334
  }
239
335
 
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
+
240
348
  /**
241
349
  * enable access of borrowed/browsed books
242
350
  */
@@ -401,6 +509,10 @@ export default class IABookActions extends LitElement {
401
509
  justify-content: center;
402
510
  flex-wrap: wrap;
403
511
  }
512
+
513
+ toast-message {
514
+ --messageHeightFromTop: 18%;
515
+ }
404
516
  `;
405
517
  }
406
518
  }
@@ -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>