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

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,15 @@
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.autoRenewConfig = {
132
+ totalTime: 60, // 60 second = 1 min
133
+ autoCheckAt: 55, // totalTime - 10th minute
134
+ FlippedInLast: 15, // within last 15 second/minute
135
+ };
124
136
 
125
137
  let lendingStatus1 = iaBookActions.lendingStatus;
138
+ let secondsLeftOnLoan = iaBookActions.autoRenewConfig.totalTime;
126
139
 
127
140
  document.querySelectorAll('.titleBar input[type=checkbox]').forEach((item) => {
128
141
  item.addEventListener('click', (e) => {
@@ -187,12 +200,24 @@
187
200
  });
188
201
 
189
202
  document.querySelectorAll('.availableToBrowse input[type=radio]').forEach((item) => {
190
- item.addEventListener('click', (e) => {
203
+ item.addEventListener('click', async (e) => {
191
204
  if (e.target.value === 'user_has_browsed') {
192
205
  lendingStatus1.user_has_browsed = true;
193
206
  lendingStatus1.available_to_browse = false;
194
- lendingStatus1.secondsLeftOnLoan = 10;
207
+ lendingStatus1.secondsLeftOnLoan = secondsLeftOnLoan;
195
208
  lendingStatus1.browsingExpired = false;
209
+
210
+ try {
211
+ // set a value
212
+ await localCache.set({
213
+ key: `${identifier}-loanTime`,
214
+ value: new Date(), // current time
215
+ ttl: Number(iaBookActions.autoRenewConfig.totalTime),
216
+ })
217
+
218
+ await localCache.delete(`${identifier}-pageFlipTime`);
219
+ } catch (error) {}
220
+
196
221
  } else if(e.target.value === 'browsingExpired') {
197
222
  lendingStatus1.user_has_browsed = true;
198
223
  lendingStatus1.available_to_browse = false;
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-alpha10",
4
4
  "description": "Bookreader actions for borrowable book",
5
5
  "author": "Internet Archive",
6
6
  "license": "AGPL-3.0-only",
@@ -43,6 +43,12 @@
43
43
  "sinon": "^13.0.2"
44
44
  },
45
45
  "eslintConfig": {
46
+ "rules": {
47
+ "no-console": "off",
48
+ "no-unused-expressions": "off",
49
+ "class-methods-use-this": "off",
50
+ "lit-a11y/click-events-have-key-events": "off"
51
+ },
46
52
  "extends": [
47
53
  "@open-wc/eslint-config",
48
54
  "eslint-config-prettier"
@@ -54,7 +60,6 @@
54
60
  },
55
61
  "husky": {
56
62
  "hooks": {
57
- "pre-commit": "lint-staged"
58
63
  }
59
64
  },
60
65
  "lint-staged": {
@@ -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,
@@ -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,21 @@ export class CollapsibleActionGroup extends ActionsHandler {
56
58
  ) {
57
59
  this.resetActions();
58
60
  }
61
+
62
+ if (changed.has('browseAgainNow') && this.browseAgainNow === true) {
63
+ this.dispatchAutoRenewBookEvent();
64
+ }
65
+ }
66
+
67
+ // dispatch this event to loan renew handler
68
+ dispatchAutoRenewBookEvent() {
69
+ this.dispatchEvent(
70
+ new CustomEvent('autoRenewLoan', {
71
+ detail: {
72
+ event: { autoRenewed1Hour: 'browseBook' },
73
+ },
74
+ })
75
+ );
59
76
  }
60
77
 
61
78
  /**
@@ -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,114 @@
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
+ };
9
+ }
10
+
11
+ constructor() {
12
+ super();
13
+ this.time = 0;
14
+ this.autoCheckAt = 0;
15
+
16
+ // private props
17
+ this.timerInterval = undefined;
18
+ }
19
+
20
+ disconnectedCallback() {
21
+ clearInterval(this.timerInterval);
22
+ }
23
+
24
+ updated(changed) {
25
+ if (changed.has('time') && this.time > 0) {
26
+ clearInterval(this.timerInterval);
27
+ this.timerCountdown();
28
+ }
29
+ }
30
+
31
+ timerCountdown() {
32
+ this.timerInterval = setInterval(() => {
33
+ this.time -= 1;
34
+
35
+ // execute 50th minute check
36
+ if (Math.round(this.time) === this.autoCheckAt) {
37
+ this.dispatchEvent(
38
+ new CustomEvent('autoRenewAttempt', {
39
+ detail: {
40
+ bookPageChange: false,
41
+ },
42
+ bubbles: true,
43
+ composed: true,
44
+ })
45
+ );
46
+ }
47
+
48
+ if (this.time === 0) clearInterval(this.timerInterval);
49
+ }, 1000);
50
+ }
51
+
52
+ render() {
53
+ return html`<div id="countdown">
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>`;
59
+ }
60
+
61
+ static get styles() {
62
+ return css`
63
+ :host {
64
+ right: 5px;
65
+ font-size: 14px;
66
+ position: absolute;
67
+ }
68
+
69
+ #countdown-number {
70
+ color: white;
71
+ display: inline-block;
72
+ line-height: 40px;
73
+ margin-right: 12px;
74
+ }
75
+
76
+ svg {
77
+ position: absolute;
78
+ top: 0;
79
+ right: 0;
80
+ width: 40px;
81
+ height: 40px;
82
+ transform: rotateY(-180deg) rotateZ(-90deg);
83
+ }
84
+
85
+ svg circle {
86
+ stroke-dasharray: 113px;
87
+ stroke-dashoffset: 0px;
88
+ stroke-linecap: round;
89
+ stroke-width: 3px;
90
+ stroke: white;
91
+ fill: none;
92
+ animation: countdown 60s linear infinite forwards;
93
+ }
94
+
95
+ @keyframes countdown {
96
+ from {
97
+ stroke-dashoffset: 0px;
98
+ }
99
+ to {
100
+ stroke-dashoffset: 113px;
101
+ }
102
+ }
103
+
104
+ .hidden {
105
+ display: none;
106
+ }
107
+ .visible {
108
+ display: inline-block;
109
+ }
110
+ `;
111
+ }
112
+ }
113
+
114
+ window.customElements.define('timer-countdown', TimerCountdown);
@@ -0,0 +1,80 @@
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: Boolean },
8
+ hideToast: { type: Boolean },
9
+ clostAfter: { type: Number },
10
+ };
11
+ }
12
+
13
+ constructor() {
14
+ super();
15
+ this.texts = '';
16
+ this.textClass = '';
17
+ this.hideToast = false;
18
+ this.clostAfter = 0;
19
+ }
20
+
21
+ render() {
22
+ return !this.hideToast
23
+ ? html`
24
+ <span
25
+ class="toast-message fade-in ${this.textClass}"
26
+ title="Click/tap to close"
27
+ @click=${() => {
28
+ this.dismisOnClick ? (this.hideToast = true) : nothing;
29
+ }}
30
+ >${this.texts}</span
31
+ >
32
+ `
33
+ : nothing;
34
+ }
35
+
36
+ static get styles() {
37
+ const heightFromTop = css`var(--messageHeightFromTop, 15%)`;
38
+
39
+ return css`
40
+ :host {
41
+ display: inline-block;
42
+ }
43
+ .toast-message {
44
+ z-index: 2;
45
+ position: absolute;
46
+ top: ${heightFromTop};
47
+ font-size: 1.4rem;
48
+ font-family: 'Helvetica Neue';
49
+ color: #fff;
50
+ left: 50%;
51
+ background: #333;
52
+ border: 1px solid #fff;
53
+ border-radius: 5px;
54
+ box-shadow: 1px 1px 2px;
55
+ padding: 10px;
56
+ width: fit-content;
57
+ transform: translate(-50%, -50%);
58
+ cursor: pointer;
59
+ animation: fadeIn 5s;
60
+ @keyframes fadeIn {
61
+ 0% {
62
+ opacity: 0;
63
+ }
64
+ 100% {
65
+ opacity: 1;
66
+ }
67
+ }
68
+ }
69
+
70
+ .hidden {
71
+ display: none;
72
+ }
73
+ .visible {
74
+ display: inline-block;
75
+ }
76
+ `;
77
+ }
78
+ }
79
+
80
+ window.customElements.define('toast-message', ToastMessage);
@@ -15,10 +15,9 @@ import * as Cookies from '../doc-cookies.js';
15
15
  export default class ActionsHandler extends LitElement {
16
16
  constructor() {
17
17
  super();
18
- this.identifier = '';
19
18
  this.ajaxTimeout = 6000;
19
+
20
20
  this.bindEvents();
21
- this.returnUrl = '';
22
21
  }
23
22
 
24
23
  sendEvent(eventCategory, eventAction) {
@@ -44,6 +43,12 @@ export default class ActionsHandler extends LitElement {
44
43
  this.sendEvent(category, action);
45
44
  });
46
45
 
46
+ this.addEventListener('autoRenewLoan', ({ detail }) => {
47
+ this.handleAutoRenewLoan();
48
+ const { category, action } = detail.event;
49
+ this.sendEvent(category, action);
50
+ });
51
+
47
52
  this.addEventListener('returnNow', ({ detail }) => {
48
53
  this.handleReturnIt();
49
54
  const { category, action } = detail.event;
@@ -98,6 +103,7 @@ export default class ActionsHandler extends LitElement {
98
103
  handleBrowseIt() {
99
104
  const action = 'browse_book';
100
105
  this.dispatchToggleActionGroup();
106
+ this.setBrowseTimeSession();
101
107
 
102
108
  ActionsHandlerService({
103
109
  action,
@@ -111,6 +117,20 @@ export default class ActionsHandler extends LitElement {
111
117
  });
112
118
  }
113
119
 
120
+ handleAutoRenewLoan() {
121
+ const action = 'renew_loan';
122
+ this.setBrowseTimeSession();
123
+
124
+ ActionsHandlerService({
125
+ action,
126
+ identifier: this.identifier,
127
+ success: () => {},
128
+ error: data => {
129
+ this.dispatchActionError(action, data);
130
+ },
131
+ });
132
+ }
133
+
114
134
  handleReturnIt() {
115
135
  const action = 'return_loan';
116
136
  this.dispatchToggleActionGroup();
@@ -261,6 +281,24 @@ export default class ActionsHandler extends LitElement {
261
281
  }
262
282
  }
263
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.autoRenewConfig.totalTime),
293
+ });
294
+
295
+ // delete flip time
296
+ await this.localCache.delete(`${this.identifier}-pageFlipTime`);
297
+ } catch (error) {
298
+ console.log(error);
299
+ }
300
+ }
301
+
264
302
  deleteLoanCookies() {
265
303
  const date = new Date();
266
304
  date.setTime(date.getTime() - 24 * 60 * 60 * 1000); // one day ago
@@ -0,0 +1,157 @@
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 OneHourAutoRenew {
8
+ constructor(
9
+ isPageClick,
10
+ identifier,
11
+ localCache,
12
+ autoRenewConfig
13
+ ) {
14
+ this.isPageClick = isPageClick;
15
+ this.identifier = identifier;
16
+ this.localCache = localCache;
17
+ this.autoRenewConfig = autoRenewConfig;
18
+
19
+ // messages for auto return machenism
20
+ this.autoRenewMessage = 'This book has been renewed for another hour.';
21
+ this.autoReturnMessage =
22
+ 'This book has been auto-returned due to inactivity.';
23
+ this.autoReturnWarning =
24
+ 'This book will be auto-returned in 10 minutes unless you turn a page.';
25
+
26
+ // private props
27
+ this.autoRenewResult = {
28
+ toastTexts: '',
29
+ hideToast: true,
30
+ browseAgainNow: false,
31
+ };
32
+
33
+ return this.handleBookAutoRenew();
34
+ }
35
+
36
+ handleBookAutoRenew() {
37
+ try {
38
+ // when user click/flip on book page
39
+ if (this.isPageClick) {
40
+ console.log('page flipped!!');
41
+ return this.userFlippedBookPage(); // user clicked on page
42
+ }
43
+
44
+ return this.autoCheckerTimer(); // auto checker at 50th minute
45
+ } catch (error) {
46
+ console.log(error);
47
+ }
48
+
49
+ return nothing;
50
+ }
51
+
52
+ // set new pageFlipTime in indexedDB
53
+ async setPageFlippedTime() {
54
+ await this.localCache.set({
55
+ key: `${this.identifier}-pageFlipTime`,
56
+ value: new Date(), // current time
57
+ ttl: Number(this.autoRenewConfig.totalTime),
58
+ });
59
+ }
60
+
61
+ async userFlippedBookPage() {
62
+ const {
63
+ totalTime, // 60
64
+ autoCheckAt, // 50
65
+ } = this.autoRenewConfig;
66
+ const currentTime = new Date();
67
+ const loanTime = await this.localCache.get(`${this.identifier}-loanTime`);
68
+
69
+ const lastTimeFrame = this.changeTime(
70
+ loanTime,
71
+ totalTime - autoCheckAt,
72
+ 'add'
73
+ ); // 50 seconds
74
+
75
+ // if user viewed new page in last 10 minutes, renew immediately
76
+ if (currentTime >= lastTimeFrame) {
77
+ console.log('1 YES NOW BORROW IT AGAIN NOW!');
78
+ this.autoRenewResult.toastTexts = this.autoRenewMessage;
79
+ this.autoRenewResult.hideToast = false;
80
+ this.autoRenewResult.browseAgainNow = true;
81
+
82
+ this.setNewBrowseTime();
83
+ }
84
+
85
+ this.setPageFlippedTime();
86
+ return this.autoRenewResult;
87
+ }
88
+
89
+ async autoCheckerTimer() {
90
+ const currentTime = new Date();
91
+ const { FlippedInLast } = this.autoRenewConfig;
92
+ const lastPageFlipTime = await this.localCache.get(
93
+ `${this.identifier}-pageFlipTime`
94
+ );
95
+
96
+ // last 15 min if used flipped a page.
97
+ const lastFlipTimeFrame = this.changeTime(
98
+ currentTime,
99
+ FlippedInLast,
100
+ 'sub'
101
+ ); // 15 seconds
102
+
103
+ if (
104
+ lastPageFlipTime === undefined ||
105
+ lastPageFlipTime <= lastFlipTimeFrame
106
+ ) {
107
+ // not viewed
108
+ console.log('Not viewed, warning message!');
109
+ this.autoRenewResult.toastTexts = this.autoReturnWarning;
110
+ this.autoRenewResult.browseAgainNow = false;
111
+ this.autoRenewResult.hideToast = false;
112
+ } else if (lastPageFlipTime >= lastFlipTimeFrame) {
113
+ // viewed in last time frame
114
+ console.log('Viewed, auto renew and message!');
115
+ this.autoRenewResult.toastTexts = this.autoRenewMessage;
116
+ this.autoRenewResult.browseAgainNow = true;
117
+ this.autoRenewResult.hideToast = false;
118
+
119
+ this.setNewBrowseTime();
120
+ }
121
+
122
+ return this.autoRenewResult;
123
+ }
124
+
125
+ async setNewBrowseTime() {
126
+ try {
127
+ // last 15 min if used flipped a page.
128
+ const expireDate = this.changeTime(
129
+ new Date(),
130
+ this.autoRenewConfig.totalTime,
131
+ 'add'
132
+ ); // 15 seconds
133
+
134
+ // set a value
135
+ await this.localCache.set({
136
+ key: `${this.identifier}-loanTime`,
137
+ value: expireDate, // expireDate time
138
+ ttl: Number(this.autoRenewConfig.totalTime),
139
+ });
140
+
141
+ // delete pageFlipTime when book is auto renew at nth minute
142
+ await this.localCache.delete(`${this.identifier}-pageFlipTime`);
143
+ } catch (error) {
144
+ console.log(error);
145
+ }
146
+ }
147
+
148
+ changeTime(date, minutes, op) {
149
+ if (date === undefined) return nothing;
150
+
151
+ if (op === 'sub') {
152
+ return new Date(date.getTime() - minutes * 1000);
153
+ }
154
+
155
+ return new Date(date.getTime() + minutes * 1000);
156
+ }
157
+ }
@@ -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
- import { GetLendingActions } from './core/services/get-lending-actions.js';
13
15
  import { mobileContainerWidth } from './core/config/constants.js';
16
+ import { GetLendingActions } from './core/services/get-lending-actions.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',
@@ -36,6 +40,9 @@ export default class IABookActions extends LitElement {
36
40
  disableActionGroup: { type: Boolean },
37
41
  modal: { Object },
38
42
  tokenDelay: { type: Number },
43
+ localCache: { type: Object },
44
+ autoRenewConfig: { type: Object },
45
+ autoRenewResult: { type: Object },
39
46
  };
40
47
  }
41
48
 
@@ -54,6 +61,17 @@ export default class IABookActions extends LitElement {
54
61
  this.disableActionGroup = false;
55
62
  this.modal = undefined;
56
63
  this.tokenDelay = 120000; // 2 minutes
64
+ this.localCache = new LocalCache({
65
+ namespace: '1HourLoanStorage',
66
+ });
67
+
68
+ // loan auto renew
69
+ this.autoRenewConfig = {};
70
+ this.autoRenewResult = {
71
+ toastTexts: '',
72
+ hideToast: true,
73
+ browseAgainNow: false,
74
+ };
57
75
 
58
76
  // private props
59
77
  this.primaryActions = [];
@@ -63,6 +81,9 @@ export default class IABookActions extends LitElement {
63
81
  this.lendingOptions = {};
64
82
  this.borrowType = ''; // (browsed|borrowed)
65
83
  this.consecutiveLoanCounts = 1; // consecutive loan count
84
+ this.browserTimer = undefined;
85
+
86
+ this.bindAutoRenewEvent();
66
87
  }
67
88
 
68
89
  disconnectedCallback() {
@@ -90,12 +111,65 @@ export default class IABookActions extends LitElement {
90
111
  }
91
112
  }
92
113
 
93
- browseHasExpired() {
114
+ bindAutoRenewEvent() {
115
+ // dispatched this event from timer-countdown
116
+ this.addEventListener('autoRenewAttempt', async event => {
117
+ const isPageClick = event.detail.bookPageChange;
118
+
119
+ // early return if user is not browsed book
120
+ if (isPageClick && this.borrowType !== 'browsed') return nothing;
121
+
122
+ this.autoRenewResult = await new OneHourAutoRenew(
123
+ isPageClick,
124
+ this.identifier,
125
+ this.localCache,
126
+ this.autoRenewConfig
127
+ );
128
+
129
+ this.toastTexts = this.autoRenewResult.toastTexts;
130
+ this.hideToast = this.autoRenewResult.hideToast;
131
+ this.browseAgainNow = this.autoRenewResult.browseAgainNow;
132
+
133
+ if (this.autoRenewResult.browseAgainNow) {
134
+ this.tokenPoller.disconnectedInterval();
135
+ await this.browseHasRenew();
136
+ }
137
+
138
+ return nothing;
139
+ });
140
+ }
141
+
142
+ async browseHasExpired() {
94
143
  const currStatus = { ...this.lendingStatus, browsingExpired: true };
95
144
  this.lendingStatus = currStatus;
145
+ this.autoRenewResult.browseAgainNow = false;
146
+
147
+ // remove respected key:value
148
+ await this.localCache.delete(`${this.identifier}-loanTime`);
149
+ await this.localCache.delete(`${this.identifier}-pageFlipTime`);
150
+
151
+ // show message after browsed book is expired.
152
+ this.autoRenewResult.toastTexts = 'This book has been auto-returned due to inactivity.';
153
+ }
154
+
155
+ async browseHasRenew() {
156
+ const loanTime = await this.localCache.get(`${this.identifier}-loanTime`);
157
+ const secondsLeft = (loanTime - new Date()) / 1000; // different in seconds
158
+
159
+ const currStatus = {
160
+ ...this.lendingStatus,
161
+ user_has_browsed: true,
162
+ browsingExpired: false,
163
+ secondsLeftOnLoan: secondsLeft,
164
+ };
165
+ this.lendingStatus = currStatus;
166
+
167
+ this.startBrowseTimer();
96
168
  }
97
169
 
98
170
  startBrowseTimer() {
171
+ clearTimeout(this.browserTimer);
172
+
99
173
  const {
100
174
  browsingExpired,
101
175
  user_has_browsed,
@@ -106,7 +180,7 @@ export default class IABookActions extends LitElement {
106
180
  }
107
181
 
108
182
  const timeLeft = secondsLeftOnLoan * 1000;
109
- setTimeout(() => {
183
+ this.browserTimer = setTimeout(() => {
110
184
  this.browseHasExpired();
111
185
  }, timeLeft);
112
186
  }
@@ -169,7 +243,11 @@ export default class IABookActions extends LitElement {
169
243
  'browsingExpired' in this.lendingStatus &&
170
244
  this.lendingStatus?.browsingExpired;
171
245
  if (hasExpired) {
172
- window?.Sentry?.captureMessage('setupLendingToolbarActions hasExpired');
246
+ if (!this.tokenPoller.disconnectedInterval) {
247
+ window?.Sentry?.captureMessage(
248
+ 'setupLendingToolbarActions hasExpired - no tokenPoller'
249
+ );
250
+ }
173
251
  this.tokenPoller?.disconnectedInterval();
174
252
  /** Global event - always fire */
175
253
  this.dispatchEvent(
@@ -194,7 +272,13 @@ export default class IABookActions extends LitElement {
194
272
  return;
195
273
  }
196
274
 
197
- this.startLoanTokenPoller();
275
+ // if (book is about to renew)
276
+ // - start token to poller around this.tokenDelay time until renew is completed
277
+ // else
278
+ // - start token poller immediately
279
+ setTimeout(() => {
280
+ this.startLoanTokenPoller();
281
+ }, this.autoRenewResult.browseAgainNow ? this.tokenDelay : 100);
198
282
  }
199
283
 
200
284
  render() {
@@ -216,8 +300,28 @@ export default class IABookActions extends LitElement {
216
300
  ></book-title-bar>`;
217
301
  }
218
302
 
303
+ get timerCountdownTemplate() {
304
+ return this.borrowType === 'browsed'
305
+ ? html`<timer-countdown
306
+ time=${Number(this.lendingStatus.secondsLeftOnLoan)}
307
+ .autoCheckAt=${this.autoRenewConfig.autoCheckAt}
308
+ ></timer-countdown>`
309
+ : nothing;
310
+ }
311
+
312
+ get toastMessageTemplate() {
313
+ return html`
314
+ <toast-message
315
+ .texts=${this.autoRenewResult.toastTexts}
316
+ ?dismisOnClick=${Boolean(true)}
317
+ ?hideToast=${this.autoRenewResult.hideToast}
318
+ ></toast-message>
319
+ `;
320
+ }
321
+
219
322
  get bookActionBar() {
220
323
  return html`
324
+ <button @click=${() => this.pageFlipEvent()}>Page Flip</button>
221
325
  <collapsible-action-group
222
326
  .userid=${this.userid}
223
327
  .identifier=${this.identifier}
@@ -227,16 +331,32 @@ export default class IABookActions extends LitElement {
227
331
  .width=${this.width}
228
332
  .borrowType=${this.borrowType}
229
333
  .returnUrl=${this.returnUrl}
334
+ .localCache=${this.localCache}
335
+ .autoRenewConfig=${this.autoRenewConfig}
230
336
  ?hasAdminAccess=${this.hasAdminAccess}
231
337
  ?disabled=${this.disableActionGroup}
338
+ ?browseAgainNow=${this.autoRenewResult.browseAgainNow}
232
339
  @lendingActionError=${this.handleLendingActionError}
233
340
  @toggleActionGroup=${this.handleToggleActionGroup}
234
341
  >
235
342
  </collapsible-action-group>
236
343
  ${this.textGroupTemplate} ${this.infoIconTemplate}
344
+ ${this.timerCountdownTemplate} ${this.toastMessageTemplate}
237
345
  `;
238
346
  }
239
347
 
348
+ // will move this function to bookreader...
349
+ pageFlipEvent() {
350
+ // dispatch event to say book page has flipped
351
+ document.querySelector('ia-book-actions').dispatchEvent(
352
+ new CustomEvent('autoRenewAttempt', {
353
+ detail: { bookPageChange: true },
354
+ bubbles: true,
355
+ composed: true,
356
+ })
357
+ );
358
+ }
359
+
240
360
  /**
241
361
  * enable access of borrowed/browsed books
242
362
  */
@@ -401,6 +521,10 @@ export default class IABookActions extends LitElement {
401
521
  justify-content: center;
402
522
  flex-wrap: wrap;
403
523
  }
524
+
525
+ toast-message {
526
+ --messageHeightFromTop: 17%;
527
+ }
404
528
  `;
405
529
  }
406
530
  }
@@ -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>