@internetarchive/ia-book-actions 0.0.29-alpha20 → 0.0.29-alpha22
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 +9 -6
- package/package.json +3 -3
- package/src/assets/data/lending-get-status-service.js +1 -1
- package/src/assets/styles/collapsible-action-group.js +2 -1
- package/src/components/info-icon.js +0 -9
- package/src/components/text-group.js +1 -1
- package/src/components/timer-countdown.js +43 -45
- package/src/core/services/actions-handler/actions-handler.js +16 -5
- package/src/core/services/get-lending-actions.js +6 -11
- package/src/core/services/loan-renew-helper.js +159 -0
- package/src/ia-book-actions.js +190 -135
- package/test/ia-book-actions.test.js +1 -5
- package/src/core/services/one-hour-loan-renew.js +0 -148
package/demo/index.html
CHANGED
|
@@ -127,9 +127,9 @@
|
|
|
127
127
|
iaBookActions.returnUrl = 'https://openlibrary.org';
|
|
128
128
|
iaBookActions.localCache = localCache;
|
|
129
129
|
iaBookActions.loanRenewConfig = {
|
|
130
|
-
totalTime:
|
|
131
|
-
autoCheckAt:
|
|
132
|
-
pageChangedInLast:
|
|
130
|
+
totalTime: 20, // 60 second = 1 min
|
|
131
|
+
autoCheckAt: 15, // totalTime - 10th minute
|
|
132
|
+
pageChangedInLast: 15, // within last 15 second/minute
|
|
133
133
|
};
|
|
134
134
|
|
|
135
135
|
let lendingStatus1 = iaBookActions.lendingStatus;
|
|
@@ -226,8 +226,11 @@
|
|
|
226
226
|
lendingStatus1.user_has_browsed = false;
|
|
227
227
|
}
|
|
228
228
|
|
|
229
|
-
|
|
230
|
-
|
|
229
|
+
// wait for a while
|
|
230
|
+
setTimeout(() => {
|
|
231
|
+
let lending = {...defaultLendingStatus, ...lendingStatus1};
|
|
232
|
+
iaBookActions.lendingStatus = lending;
|
|
233
|
+
}, 10);
|
|
231
234
|
});
|
|
232
235
|
});
|
|
233
236
|
|
|
@@ -281,7 +284,7 @@
|
|
|
281
284
|
// dispatch event to say book page has flipped
|
|
282
285
|
document.querySelector('ia-book-actions').dispatchEvent(
|
|
283
286
|
new CustomEvent('BookReader:pageChanged', {
|
|
284
|
-
detail: {
|
|
287
|
+
detail: { hasPageChanged: true },
|
|
285
288
|
bubbles: true,
|
|
286
289
|
composed: true,
|
|
287
290
|
})
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@internetarchive/ia-book-actions",
|
|
3
|
-
"version": "0.0.29-
|
|
3
|
+
"version": "0.0.29-alpha22",
|
|
4
4
|
"description": "Bookreader actions for borrowable book",
|
|
5
5
|
"author": "Internet Archive",
|
|
6
6
|
"license": "AGPL-3.0-only",
|
|
@@ -22,8 +22,8 @@
|
|
|
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-
|
|
26
|
-
"lit": "^2.
|
|
25
|
+
"@internetarchive/toast-manager": "0.0.1-alpha8",
|
|
26
|
+
"lit": "^2.5.0"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"@open-wc/eslint-config": "^4.2.0",
|
|
@@ -118,7 +118,7 @@ export const canBrowseAndBorrow = {
|
|
|
118
118
|
loanStartDate: '2021-07-30 09:57:40',
|
|
119
119
|
loansUrl: '/details/@neeraj-archive?tab=loans#loans-on-loan',
|
|
120
120
|
maxLoans: 10,
|
|
121
|
-
secondsLeftOnLoan:
|
|
121
|
+
secondsLeftOnLoan: 12,
|
|
122
122
|
shouldProtectImages: true,
|
|
123
123
|
totalWaitlistCount: 0,
|
|
124
124
|
userHasBorrowed: false,
|
|
@@ -12,6 +12,7 @@ export default css`
|
|
|
12
12
|
}
|
|
13
13
|
.actiongroup {
|
|
14
14
|
display: flex;
|
|
15
|
+
margin-right: 10px;
|
|
15
16
|
}
|
|
16
17
|
.action-buttons {
|
|
17
18
|
display: inline-flex;
|
|
@@ -33,12 +34,12 @@ export default css`
|
|
|
33
34
|
}
|
|
34
35
|
.primary {
|
|
35
36
|
background-color: ${whiteColor};
|
|
37
|
+
margin-right: 4px;
|
|
36
38
|
}
|
|
37
39
|
.primary,
|
|
38
40
|
.secondary {
|
|
39
41
|
position: relative;
|
|
40
42
|
border-radius: 5px;
|
|
41
|
-
margin: 0 3px;
|
|
42
43
|
}
|
|
43
44
|
.primary .initial {
|
|
44
45
|
border-radius: 4px 0 0 4px;
|
|
@@ -43,21 +43,12 @@ export class InfoIcon extends LitElement {
|
|
|
43
43
|
vertical-align: middle;
|
|
44
44
|
--iconFillColor: white;
|
|
45
45
|
}
|
|
46
|
-
.more-info-icon {
|
|
47
|
-
margin-left: 10px;
|
|
48
|
-
}
|
|
49
46
|
.more-info-icon img {
|
|
50
47
|
width: 24px;
|
|
51
48
|
height: 24px;
|
|
52
49
|
vertical-align: middle;
|
|
53
50
|
background: white;
|
|
54
51
|
}
|
|
55
|
-
.more-info-icon.mobile {
|
|
56
|
-
position: absolute;
|
|
57
|
-
top: 4px;
|
|
58
|
-
right: 55px;
|
|
59
|
-
margin-top: 15px;
|
|
60
|
-
}
|
|
61
52
|
.hidden {
|
|
62
53
|
display: none;
|
|
63
54
|
}
|
|
@@ -32,12 +32,15 @@ export default class TimerCountdown extends LitElement {
|
|
|
32
32
|
this.timerInterval = setInterval(() => {
|
|
33
33
|
this.time -= 1;
|
|
34
34
|
|
|
35
|
-
// execute
|
|
36
|
-
if
|
|
35
|
+
// execute from last 10th minute to 0th minute
|
|
36
|
+
// - 10th - to check if user has viewed
|
|
37
|
+
// - till 0th - to show warning msg with remaining time to auto returned
|
|
38
|
+
if (Math.round(this.time) <= this.autoCheckAt) {
|
|
37
39
|
this.dispatchEvent(
|
|
38
40
|
new CustomEvent('IABookActions:loanRenew', {
|
|
39
41
|
detail: {
|
|
40
|
-
|
|
42
|
+
hasPageChanged: false,
|
|
43
|
+
timeLeft: Math.round(this.time),
|
|
41
44
|
},
|
|
42
45
|
bubbles: true,
|
|
43
46
|
composed: true,
|
|
@@ -45,67 +48,62 @@ export default class TimerCountdown extends LitElement {
|
|
|
45
48
|
);
|
|
46
49
|
}
|
|
47
50
|
|
|
48
|
-
if
|
|
51
|
+
// clear interval if timer is < this.autoCheckAt
|
|
52
|
+
if (this.time < this.autoCheckAt) {
|
|
53
|
+
clearInterval(this.timerInterval);
|
|
54
|
+
}
|
|
49
55
|
}, 1000);
|
|
50
56
|
}
|
|
51
57
|
|
|
52
58
|
render() {
|
|
53
|
-
return html
|
|
54
|
-
<
|
|
55
|
-
|
|
56
|
-
|
|
59
|
+
return html`
|
|
60
|
+
<svg
|
|
61
|
+
width="16"
|
|
62
|
+
height="16"
|
|
63
|
+
viewBox="0 0 100 100"
|
|
64
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
65
|
+
>
|
|
66
|
+
<circle class="circle" cx="50" cy="50" r="50" />
|
|
57
67
|
</svg>
|
|
58
|
-
|
|
68
|
+
`;
|
|
59
69
|
}
|
|
60
70
|
|
|
61
71
|
static get styles() {
|
|
72
|
+
const white = css`var(--white, #fff)`;
|
|
73
|
+
const timerSecondsLeft = css`var(--timerSeconds, 6000s)`;
|
|
74
|
+
const timerStrokeLeft = css`var(--timerStroke, 315)`;
|
|
75
|
+
|
|
62
76
|
return css`
|
|
63
77
|
:host {
|
|
64
78
|
right: 5px;
|
|
65
|
-
font-size: 14px;
|
|
66
79
|
position: absolute;
|
|
67
80
|
}
|
|
68
81
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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;
|
|
82
|
+
@keyframes circletimer {
|
|
83
|
+
0% {
|
|
84
|
+
stroke-dashoffset: ${timerStrokeLeft};
|
|
85
|
+
stroke-dasharray: 315;
|
|
98
86
|
}
|
|
99
|
-
|
|
100
|
-
stroke-dashoffset:
|
|
87
|
+
100% {
|
|
88
|
+
stroke-dashoffset: 0;
|
|
89
|
+
stroke-dasharray: 315;
|
|
101
90
|
}
|
|
102
91
|
}
|
|
103
92
|
|
|
104
|
-
|
|
105
|
-
|
|
93
|
+
svg {
|
|
94
|
+
background-color: ${white};
|
|
95
|
+
border-radius: 50px;
|
|
96
|
+
border: 2px solid ${white};
|
|
97
|
+
transform: rotateZ(-90deg);
|
|
106
98
|
}
|
|
107
|
-
|
|
108
|
-
|
|
99
|
+
|
|
100
|
+
svg .circle {
|
|
101
|
+
stroke: #000;
|
|
102
|
+
stroke-width: 100px;
|
|
103
|
+
fill: ${white};
|
|
104
|
+
stroke-dashoffset: 315;
|
|
105
|
+
stroke-dasharray: 0;
|
|
106
|
+
animation: ${timerSecondsLeft} circletimer linear;
|
|
109
107
|
}
|
|
110
108
|
`;
|
|
111
109
|
}
|
|
@@ -119,12 +119,19 @@ export default class ActionsHandler extends LitElement {
|
|
|
119
119
|
|
|
120
120
|
handleLoanRenew() {
|
|
121
121
|
const action = 'renew_loan';
|
|
122
|
-
this.setBrowseTimeSession();
|
|
123
122
|
|
|
124
123
|
ActionsHandlerService({
|
|
125
124
|
action,
|
|
126
125
|
identifier: this.identifier,
|
|
127
|
-
success:
|
|
126
|
+
success: data => {
|
|
127
|
+
// success analytics by isa?
|
|
128
|
+
this.setBrowseTimeSession();
|
|
129
|
+
this.dispatchEvent(
|
|
130
|
+
new CustomEvent('loanAutoRenewed', {
|
|
131
|
+
detail: { action, data },
|
|
132
|
+
})
|
|
133
|
+
);
|
|
134
|
+
},
|
|
128
135
|
error: data => {
|
|
129
136
|
this.dispatchActionError(action, data);
|
|
130
137
|
},
|
|
@@ -286,13 +293,17 @@ export default class ActionsHandler extends LitElement {
|
|
|
286
293
|
*/
|
|
287
294
|
async setBrowseTimeSession() {
|
|
288
295
|
try {
|
|
296
|
+
const { totalTime } = this.loanRenewConfig;
|
|
297
|
+
const expireDate = new Date(new Date().getTime() + totalTime * 1000);
|
|
298
|
+
|
|
299
|
+
// set a value
|
|
289
300
|
await this.localCache.set({
|
|
290
301
|
key: `${this.identifier}-loanTime`,
|
|
291
|
-
value:
|
|
292
|
-
ttl: Number(
|
|
302
|
+
value: expireDate,
|
|
303
|
+
ttl: Number(totalTime),
|
|
293
304
|
});
|
|
294
305
|
|
|
295
|
-
// delete
|
|
306
|
+
// delete pageChangedTime when book is auto renew at nth minute
|
|
296
307
|
await this.localCache.delete(`${this.identifier}-pageChangedTime`);
|
|
297
308
|
} catch (error) {
|
|
298
309
|
console.log(error);
|
|
@@ -19,7 +19,7 @@ import ActionsConfig from './actions-config.js';
|
|
|
19
19
|
*
|
|
20
20
|
* Response of this class contain an object as follows:-
|
|
21
21
|
* {
|
|
22
|
-
* primaryTitle: '
|
|
22
|
+
* primaryTitle: '',
|
|
23
23
|
* primaryActions: [this.actionsConfig.returnBookConfig()],
|
|
24
24
|
* primaryColor: 'primary',
|
|
25
25
|
* secondaryActions: [this.actionsConfig.purchaseConfig()],
|
|
@@ -40,7 +40,7 @@ import ActionsConfig from './actions-config.js';
|
|
|
40
40
|
* @enum {string}
|
|
41
41
|
*/
|
|
42
42
|
export const bookTitles = {
|
|
43
|
-
available_1hr: '
|
|
43
|
+
available_1hr: '',
|
|
44
44
|
available_14d: 'This book can be borrowed for 14 days.',
|
|
45
45
|
available_pd: 'Book available to patrons with print disabilities.',
|
|
46
46
|
available_waitlist: 'A waitlist is available.',
|
|
@@ -49,7 +49,7 @@ export const bookTitles = {
|
|
|
49
49
|
being_borrowed: 'Another patron is using this book. Please check back later.',
|
|
50
50
|
eligible_pd: 'You are eligible for print-disabled access.',
|
|
51
51
|
on_waitlist: 'You are on the waitlist for this book.',
|
|
52
|
-
session_expired: '
|
|
52
|
+
session_expired: '',
|
|
53
53
|
unavailable: 'This book is not available at this time.',
|
|
54
54
|
};
|
|
55
55
|
|
|
@@ -98,7 +98,7 @@ export class GetLendingActions {
|
|
|
98
98
|
let isBrowsing =
|
|
99
99
|
lendingStatus.user_has_browsed && !lendingStatus.browsingExpired;
|
|
100
100
|
if (isBrowsing) {
|
|
101
|
-
primaryTitleText =
|
|
101
|
+
primaryTitleText = '';
|
|
102
102
|
} else {
|
|
103
103
|
primaryTitleText = `Your loan of this book has ${lendingStatus.daysLeftOnLoan} days left.`;
|
|
104
104
|
}
|
|
@@ -251,13 +251,6 @@ export class GetLendingActions {
|
|
|
251
251
|
borrow1HrAction() {
|
|
252
252
|
const lendingStatus = this.lendingStatus || {};
|
|
253
253
|
|
|
254
|
-
const possibleTitles = {
|
|
255
|
-
one_hour: 'Renewable every hour, pending availability.',
|
|
256
|
-
session_tryagain:
|
|
257
|
-
'Another patron is using this book. Please check back later.',
|
|
258
|
-
session_expired: 'Your loan has expired.',
|
|
259
|
-
};
|
|
260
|
-
|
|
261
254
|
// assess borrowable state
|
|
262
255
|
const browsingHasExpired =
|
|
263
256
|
!lendingStatus.available_to_browse && lendingStatus.browsingExpired;
|
|
@@ -364,6 +357,8 @@ export class GetLendingActions {
|
|
|
364
357
|
}
|
|
365
358
|
|
|
366
359
|
/**
|
|
360
|
+
* @deprecated
|
|
361
|
+
*
|
|
367
362
|
* Builds the countdown toolbar title.
|
|
368
363
|
*
|
|
369
364
|
* @return string innerHTML for the dialogOpts title attribute
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { nothing } from 'lit';
|
|
2
|
+
import { ToastConfig } from '@internetarchive/toast-manager';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* This class is used to determine if use is eligible for auto renew loan.
|
|
6
|
+
*/
|
|
7
|
+
export class LoanRenewHelper {
|
|
8
|
+
constructor(hasPageChanged, identifier, localCache, loanRenewConfig) {
|
|
9
|
+
this.hasPageChanged = hasPageChanged;
|
|
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 1 hour.';
|
|
16
|
+
this.loanReturnWarning =
|
|
17
|
+
'This book will be automatically returned in #time minutes unless you turn a page.';
|
|
18
|
+
|
|
19
|
+
// private props
|
|
20
|
+
this.result = {
|
|
21
|
+
texts: null,
|
|
22
|
+
renewNow: false,
|
|
23
|
+
timeLeft: 0,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
handleLoanRenew() {
|
|
28
|
+
try {
|
|
29
|
+
if (this.hasPageChanged) {
|
|
30
|
+
// when user click/flip on book page
|
|
31
|
+
this.showToastMessage();
|
|
32
|
+
return this.pageChanged(); // user clicked on page
|
|
33
|
+
}
|
|
34
|
+
return this.autoChecker(); // auto checker at 50th minute
|
|
35
|
+
} catch (error) {
|
|
36
|
+
console.log(error);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return nothing;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Trigger this function when user has browsed a book and change the post
|
|
44
|
+
* - every time user change the page, we set current time in indexedDB
|
|
45
|
+
* - also check if need to auto renew current loan
|
|
46
|
+
*
|
|
47
|
+
* @returns {this.result}
|
|
48
|
+
*/
|
|
49
|
+
async pageChanged() {
|
|
50
|
+
const {
|
|
51
|
+
totalTime, // 60
|
|
52
|
+
autoCheckAt, // 50
|
|
53
|
+
} = this.loanRenewConfig;
|
|
54
|
+
const currentTime = new Date();
|
|
55
|
+
const loanTime = await this.localCache.get(`${this.identifier}-loanTime`);
|
|
56
|
+
|
|
57
|
+
const lastTimeFrame = this.changeTime(
|
|
58
|
+
loanTime,
|
|
59
|
+
totalTime - autoCheckAt,
|
|
60
|
+
'add'
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
// if user viewed new page in last 10 minutes, renew immediately
|
|
64
|
+
if (currentTime >= lastTimeFrame) {
|
|
65
|
+
this.result.texts = this.loanRenewMessage;
|
|
66
|
+
this.result.renewNow = true;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
this.setPageChangedTime();
|
|
70
|
+
return this.result;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Trigger this function when countdown hits last 10 minutes
|
|
75
|
+
* - if user is open new page, just renew the loan at last 10th minute
|
|
76
|
+
*
|
|
77
|
+
* @returns {this.result}
|
|
78
|
+
*/
|
|
79
|
+
async autoChecker() {
|
|
80
|
+
const currentTime = new Date();
|
|
81
|
+
const { pageChangedInLast } = this.loanRenewConfig;
|
|
82
|
+
const lastPageFlipTime = await this.localCache.get(
|
|
83
|
+
`${this.identifier}-pageChangedTime`
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
// last 15 min if used flipped a page.
|
|
87
|
+
const lastFlipTimeFrame = this.changeTime(
|
|
88
|
+
currentTime,
|
|
89
|
+
pageChangedInLast,
|
|
90
|
+
'sub'
|
|
91
|
+
); // 15 seconds
|
|
92
|
+
|
|
93
|
+
if (
|
|
94
|
+
lastPageFlipTime === undefined ||
|
|
95
|
+
lastPageFlipTime <= lastFlipTimeFrame
|
|
96
|
+
) {
|
|
97
|
+
this.result.texts = this.loanReturnWarning;
|
|
98
|
+
this.result.renewNow = false; // not viewed
|
|
99
|
+
this.showToastMessage();
|
|
100
|
+
} else if (lastPageFlipTime >= lastFlipTimeFrame) {
|
|
101
|
+
this.result.texts = '';
|
|
102
|
+
this.result.renewNow = true; // viewed in last time frame
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return this.result;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* set current time in indexedDB when user viewed a new page
|
|
110
|
+
*/
|
|
111
|
+
async setPageChangedTime() {
|
|
112
|
+
await this.localCache.set({
|
|
113
|
+
key: `${this.identifier}-pageChangedTime`,
|
|
114
|
+
value: new Date(), // current time
|
|
115
|
+
ttl: Number(this.loanRenewConfig.totalTime),
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Show toast messages on some specific loan renew features. e.g.
|
|
121
|
+
* - show success msg when book is auto renewed
|
|
122
|
+
* - show success msg when book is auto returned
|
|
123
|
+
* - show warninig msg when book is about to auto returned
|
|
124
|
+
*/
|
|
125
|
+
async showToastMessage() {
|
|
126
|
+
const iaBookActions = document.querySelector('ia-book-actions').shadowRoot;
|
|
127
|
+
let toastTemplate = iaBookActions.querySelector('toast-template');
|
|
128
|
+
if (!toastTemplate) {
|
|
129
|
+
toastTemplate = document.createElement('toast-template');
|
|
130
|
+
}
|
|
131
|
+
await iaBookActions.appendChild(toastTemplate);
|
|
132
|
+
|
|
133
|
+
const config = new ToastConfig();
|
|
134
|
+
config.texts = this.result.texts?.replace(/#time/, this.result.timeLeft);
|
|
135
|
+
config.dismisOnClick = true;
|
|
136
|
+
toastTemplate.showToast({
|
|
137
|
+
config,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Helper function to get time difference
|
|
143
|
+
*
|
|
144
|
+
* @param {Date} date
|
|
145
|
+
* @param {Number} minutes
|
|
146
|
+
* @param {String} op
|
|
147
|
+
*
|
|
148
|
+
* @returns {Object} Date
|
|
149
|
+
*/
|
|
150
|
+
changeTime(date, minutes, op) {
|
|
151
|
+
if (date === undefined) return null;
|
|
152
|
+
|
|
153
|
+
if (op === 'sub') {
|
|
154
|
+
return new Date(date.getTime() - minutes * 1000);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return new Date(date.getTime() + minutes * 1000);
|
|
158
|
+
}
|
|
159
|
+
}
|
package/src/ia-book-actions.js
CHANGED
|
@@ -3,8 +3,6 @@ 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';
|
|
8
6
|
import { LocalCache } from '@internetarchive/local-cache';
|
|
9
7
|
|
|
10
8
|
import './components/collapsible-action-group.js';
|
|
@@ -13,10 +11,10 @@ import './components/text-group.js';
|
|
|
13
11
|
import './components/info-icon.js';
|
|
14
12
|
import './components/timer-countdown.js';
|
|
15
13
|
|
|
16
|
-
import { mobileContainerWidth } from './core/config/constants.js';
|
|
17
14
|
import { GetLendingActions } from './core/services/get-lending-actions.js';
|
|
15
|
+
import { mobileContainerWidth } from './core/config/constants.js';
|
|
18
16
|
import { LoanTokenPoller } from './core/services/loan-token-poller.js';
|
|
19
|
-
import {
|
|
17
|
+
import { LoanRenewHelper } from './core/services/loan-renew-helper.js';
|
|
20
18
|
|
|
21
19
|
export const events = {
|
|
22
20
|
browseExpired: 'IABookReader:BrowsingHasExpired',
|
|
@@ -62,17 +60,6 @@ export default class IABookActions extends LitElement {
|
|
|
62
60
|
this.disableActionGroup = false;
|
|
63
61
|
this.modal = undefined;
|
|
64
62
|
this.tokenDelay = 120000; // 2 minutes
|
|
65
|
-
this.localCache = new LocalCache({
|
|
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
|
-
};
|
|
76
63
|
|
|
77
64
|
// private props
|
|
78
65
|
this.primaryActions = [];
|
|
@@ -82,19 +69,39 @@ export default class IABookActions extends LitElement {
|
|
|
82
69
|
this.lendingOptions = {};
|
|
83
70
|
this.borrowType = ''; // (browsed|borrowed)
|
|
84
71
|
this.consecutiveLoanCounts = 1; // consecutive loan count
|
|
72
|
+
|
|
73
|
+
// loan auto renew
|
|
85
74
|
this.browserTimer = undefined;
|
|
75
|
+
this.loanRenewConfig = {};
|
|
86
76
|
|
|
87
|
-
|
|
88
|
-
|
|
77
|
+
/**
|
|
78
|
+
* contains loan auto-renew response
|
|
79
|
+
* - texts:- texts messages need to show in toast message
|
|
80
|
+
* - renewNow:- key to determine if need to renew now
|
|
81
|
+
*/
|
|
82
|
+
this.loanRenewResult = {
|
|
83
|
+
texts: null,
|
|
84
|
+
renewNow: false,
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
this._shadowRoot = this.attachShadow({ mode: 'open' });
|
|
89
88
|
}
|
|
90
89
|
|
|
91
90
|
disconnectedCallback() {
|
|
92
|
-
this.tokenPoller
|
|
91
|
+
this.tokenPoller.disconnectedInterval();
|
|
93
92
|
window?.Sentry?.captureMessage('disconnectedCallback');
|
|
94
93
|
this.disconnectResizeObserver();
|
|
95
94
|
}
|
|
96
95
|
|
|
97
96
|
firstUpdated() {
|
|
97
|
+
// bind auto loan renew events
|
|
98
|
+
this.bindLoanRenewEvents();
|
|
99
|
+
|
|
100
|
+
// localCache used for auto-loan-renew
|
|
101
|
+
this.localCache = new LocalCache({
|
|
102
|
+
namespace: 'loanRenew',
|
|
103
|
+
});
|
|
104
|
+
|
|
98
105
|
if (!this.sharedObserver) {
|
|
99
106
|
this.sharedObserver = new SharedResizeObserver();
|
|
100
107
|
this.setupResizeObserver();
|
|
@@ -113,112 +120,6 @@ export default class IABookActions extends LitElement {
|
|
|
113
120
|
}
|
|
114
121
|
}
|
|
115
122
|
|
|
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();
|
|
123
|
-
}
|
|
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
|
|
170
|
-
});
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
async browseHasExpired() {
|
|
174
|
-
const currStatus = { ...this.lendingStatus, browsingExpired: true };
|
|
175
|
-
this.lendingStatus = currStatus;
|
|
176
|
-
|
|
177
|
-
// remove respected key:value
|
|
178
|
-
await this.localCache.delete(`${this.identifier}-loanTime`);
|
|
179
|
-
await this.localCache.delete(`${this.identifier}-pageChangedTime`);
|
|
180
|
-
|
|
181
|
-
// show message after browsed book is expired.
|
|
182
|
-
this.loanRenewResult.renewNow = false;
|
|
183
|
-
this.loanRenewResult.texts =
|
|
184
|
-
'This book has been auto-returned due to inactivity.';
|
|
185
|
-
|
|
186
|
-
this.showToastMessage();
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
async browseHasRenew() {
|
|
190
|
-
const loanTime = await this.localCache.get(`${this.identifier}-loanTime`);
|
|
191
|
-
const secondsLeft = (loanTime - new Date()) / 1000; // different in seconds
|
|
192
|
-
|
|
193
|
-
const currStatus = {
|
|
194
|
-
...this.lendingStatus,
|
|
195
|
-
user_has_browsed: true,
|
|
196
|
-
browsingExpired: false,
|
|
197
|
-
secondsLeftOnLoan: secondsLeft,
|
|
198
|
-
};
|
|
199
|
-
this.lendingStatus = currStatus;
|
|
200
|
-
|
|
201
|
-
this.startBrowseTimer();
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
startBrowseTimer() {
|
|
205
|
-
clearTimeout(this.browserTimer);
|
|
206
|
-
|
|
207
|
-
const {
|
|
208
|
-
browsingExpired,
|
|
209
|
-
user_has_browsed,
|
|
210
|
-
secondsLeftOnLoan,
|
|
211
|
-
} = this.lendingStatus;
|
|
212
|
-
if (!user_has_browsed || browsingExpired) {
|
|
213
|
-
return;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
const timeLeft = secondsLeftOnLoan * 1000;
|
|
217
|
-
this.browserTimer = setTimeout(() => {
|
|
218
|
-
this.browseHasExpired();
|
|
219
|
-
}, timeLeft);
|
|
220
|
-
}
|
|
221
|
-
|
|
222
123
|
/** SharedObserver resize handler */
|
|
223
124
|
handleResize(entry) {
|
|
224
125
|
// if you are observing multiple targets,
|
|
@@ -277,7 +178,7 @@ export default class IABookActions extends LitElement {
|
|
|
277
178
|
'browsingExpired' in this.lendingStatus &&
|
|
278
179
|
this.lendingStatus?.browsingExpired;
|
|
279
180
|
if (hasExpired) {
|
|
280
|
-
if (!this.tokenPoller
|
|
181
|
+
if (!this.tokenPoller) {
|
|
281
182
|
window?.Sentry?.captureMessage(
|
|
282
183
|
'setupLendingToolbarActions hasExpired - no tokenPoller'
|
|
283
184
|
);
|
|
@@ -306,18 +207,154 @@ export default class IABookActions extends LitElement {
|
|
|
306
207
|
return;
|
|
307
208
|
}
|
|
308
209
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
210
|
+
/**
|
|
211
|
+
* tokenPoller determines if user has loan token for this book
|
|
212
|
+
* - if book is going to renew, need to wait until renew is completed
|
|
213
|
+
* - otherwise execute immediately
|
|
214
|
+
*/
|
|
313
215
|
setTimeout(
|
|
314
216
|
() => {
|
|
315
217
|
this.startLoanTokenPoller();
|
|
218
|
+
|
|
219
|
+
if (!this.loanRenewResult.renewNow) {
|
|
220
|
+
this.resetTimerCountState();
|
|
221
|
+
}
|
|
316
222
|
},
|
|
317
223
|
this.loanRenewResult.renewNow ? this.tokenDelay : 100
|
|
318
224
|
);
|
|
319
225
|
}
|
|
320
226
|
|
|
227
|
+
/**
|
|
228
|
+
* Bind 1 hour loan auto renew,
|
|
229
|
+
* There are two events we want to use,
|
|
230
|
+
* 1. BookReader:pageChanged - dispatched from bookreader side
|
|
231
|
+
* 2. IABookActions:loanRenew - dispatched from timer-countdown component
|
|
232
|
+
*/
|
|
233
|
+
bindLoanRenewEvents() {
|
|
234
|
+
// dispatched this event from bookreader page changed
|
|
235
|
+
window.addEventListener('BookReader:pageChanged', event => {
|
|
236
|
+
if (this.borrowType === 'browsed' && event.detail.hasPageChanged) {
|
|
237
|
+
this.autoLoanRenewChecker(true);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return nothing;
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* this event dispatched from timer-countdown component for:
|
|
245
|
+
* 1. user turned book page after showing auto-returned warning message
|
|
246
|
+
* 2. show warning message having remaining time
|
|
247
|
+
*/
|
|
248
|
+
this.addEventListener('IABookActions:loanRenew', async event => {
|
|
249
|
+
const eventDetails = event.detail;
|
|
250
|
+
|
|
251
|
+
await this.autoLoanRenewChecker(false);
|
|
252
|
+
|
|
253
|
+
// show warning message with remaining time to auto returned it.
|
|
254
|
+
if (this.loanRenewHelper.result.renewNow === false) {
|
|
255
|
+
this.loanRenewHelper.result.timeLeft = eventDetails.timeLeft;
|
|
256
|
+
this.loanRenewHelper.showToastMessage();
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
async autoLoanRenewChecker(hasPageChanged = false) {
|
|
262
|
+
this.loanRenewHelper = new LoanRenewHelper(
|
|
263
|
+
hasPageChanged,
|
|
264
|
+
this.identifier,
|
|
265
|
+
this.localCache,
|
|
266
|
+
this.loanRenewConfig
|
|
267
|
+
);
|
|
268
|
+
|
|
269
|
+
await this.loanRenewHelper.handleLoanRenew();
|
|
270
|
+
console.log(this.loanRenewResult);
|
|
271
|
+
this.loanRenewResult = this.loanRenewHelper.result;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
async browseHasRenew() {
|
|
275
|
+
const loanTime = await this.localCache.get(`${this.identifier}-loanTime`);
|
|
276
|
+
const secondsLeft = (loanTime - new Date()) / 1000; // different in seconds
|
|
277
|
+
|
|
278
|
+
const currStatus = {
|
|
279
|
+
...this.lendingStatus,
|
|
280
|
+
user_has_browsed: true,
|
|
281
|
+
browsingExpired: false,
|
|
282
|
+
secondsLeftOnLoan: secondsLeft,
|
|
283
|
+
};
|
|
284
|
+
this.lendingStatus = currStatus;
|
|
285
|
+
|
|
286
|
+
this.startBrowseTimer();
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
startBrowseTimer() {
|
|
290
|
+
clearTimeout(this.browserTimer);
|
|
291
|
+
|
|
292
|
+
const {
|
|
293
|
+
browsingExpired,
|
|
294
|
+
user_has_browsed,
|
|
295
|
+
secondsLeftOnLoan,
|
|
296
|
+
} = this.lendingStatus;
|
|
297
|
+
if (!user_has_browsed || browsingExpired) {
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const timeLeft = secondsLeftOnLoan * 1000;
|
|
302
|
+
this.browserTimer = setTimeout(() => {
|
|
303
|
+
this.browseHasExpired();
|
|
304
|
+
}, timeLeft);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Execute when loan is expired
|
|
309
|
+
*/
|
|
310
|
+
async browseHasExpired() {
|
|
311
|
+
const currStatus = { ...this.lendingStatus, browsingExpired: true };
|
|
312
|
+
this.lendingStatus = currStatus;
|
|
313
|
+
|
|
314
|
+
// remove respected key:value for loan-renew
|
|
315
|
+
await this.localCache.delete(`${this.identifier}-loanTime`);
|
|
316
|
+
await this.localCache.delete(`${this.identifier}-pageChangedTime`);
|
|
317
|
+
|
|
318
|
+
// show message after browsed book is expired.
|
|
319
|
+
this.loanRenewResult.renewNow = false;
|
|
320
|
+
this.loanRenewResult.texts =
|
|
321
|
+
'This book has been automatically returned due to inactivity.';
|
|
322
|
+
|
|
323
|
+
this.loanRenewHelper.showToastMessage();
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Reset timer animation state after book renewed
|
|
328
|
+
*/
|
|
329
|
+
async resetTimerCountState() {
|
|
330
|
+
const secondsLeft = Number(this.lendingStatus.secondsLeftOnLoan);
|
|
331
|
+
|
|
332
|
+
const timerCountdown = this.shadowRoot.querySelector('timer-countdown');
|
|
333
|
+
const animationCircle = timerCountdown.shadowRoot.querySelector('.circle');
|
|
334
|
+
|
|
335
|
+
// the perimeter of the circle
|
|
336
|
+
const circleStroke = 315; // (π * 2 * radius)
|
|
337
|
+
const strokeDashOffset =
|
|
338
|
+
(secondsLeft / this.loanRenewConfig.totalTime) *
|
|
339
|
+
circleStroke;
|
|
340
|
+
|
|
341
|
+
// set seconds left in loan expire
|
|
342
|
+
timerCountdown.style?.setProperty(
|
|
343
|
+
'--timerSeconds',
|
|
344
|
+
`${secondsLeft}s`
|
|
345
|
+
);
|
|
346
|
+
// set circle stroke offset left in loan expire
|
|
347
|
+
timerCountdown.style?.setProperty(
|
|
348
|
+
'--timerStroke',
|
|
349
|
+
`${Number(strokeDashOffset)}`
|
|
350
|
+
);
|
|
351
|
+
|
|
352
|
+
animationCircle.style.animationName = 'none';
|
|
353
|
+
setTimeout(() => {
|
|
354
|
+
animationCircle.style.animationName = 'circletimer';
|
|
355
|
+
}, 100);
|
|
356
|
+
}
|
|
357
|
+
|
|
321
358
|
render() {
|
|
322
359
|
if (this.barType === 'title') {
|
|
323
360
|
return html`<section class="lending-wrapper">
|
|
@@ -340,7 +377,7 @@ export default class IABookActions extends LitElement {
|
|
|
340
377
|
get timerCountdownTemplate() {
|
|
341
378
|
return this.borrowType === 'browsed'
|
|
342
379
|
? html`<timer-countdown
|
|
343
|
-
time=${Number(this.lendingStatus.secondsLeftOnLoan)}
|
|
380
|
+
.time=${Number(this.lendingStatus.secondsLeftOnLoan)}
|
|
344
381
|
.autoCheckAt=${this.loanRenewConfig.autoCheckAt}
|
|
345
382
|
></timer-countdown>`
|
|
346
383
|
: nothing;
|
|
@@ -362,6 +399,7 @@ export default class IABookActions extends LitElement {
|
|
|
362
399
|
?hasAdminAccess=${this.hasAdminAccess}
|
|
363
400
|
?disabled=${this.disableActionGroup}
|
|
364
401
|
?renewNow=${this.loanRenewResult.renewNow}
|
|
402
|
+
@loanAutoRenewed=${this.handleLoanAutoRenewed}
|
|
365
403
|
@lendingActionError=${this.handleLendingActionError}
|
|
366
404
|
@toggleActionGroup=${this.handleToggleActionGroup}
|
|
367
405
|
>
|
|
@@ -371,6 +409,21 @@ export default class IABookActions extends LitElement {
|
|
|
371
409
|
`;
|
|
372
410
|
}
|
|
373
411
|
|
|
412
|
+
/**
|
|
413
|
+
* Execute after auto loan renewed is completed
|
|
414
|
+
* - show success message
|
|
415
|
+
* - then change the remaining time
|
|
416
|
+
* - reset timer state
|
|
417
|
+
*/
|
|
418
|
+
async handleLoanAutoRenewed() {
|
|
419
|
+
if (this.loanRenewResult.renewNow) {
|
|
420
|
+
this.tokenPoller.disconnectedInterval();
|
|
421
|
+
await this.loanRenewHelper.showToastMessage();
|
|
422
|
+
await this.browseHasRenew();
|
|
423
|
+
await this.resetTimerCountState();
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
374
427
|
/**
|
|
375
428
|
* enable access of borrowed/browsed books
|
|
376
429
|
*/
|
|
@@ -509,11 +562,13 @@ export default class IABookActions extends LitElement {
|
|
|
509
562
|
}
|
|
510
563
|
|
|
511
564
|
get textGroupTemplate() {
|
|
512
|
-
return
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
565
|
+
return this.primaryTitle
|
|
566
|
+
? html`<text-group
|
|
567
|
+
textClass=${this.textClass}
|
|
568
|
+
.texts=${this.primaryTitle}
|
|
569
|
+
>
|
|
570
|
+
</text-group>`
|
|
571
|
+
: nothing;
|
|
517
572
|
}
|
|
518
573
|
|
|
519
574
|
get hasAdminAccess() {
|
|
@@ -182,9 +182,7 @@ describe('Borrow status actions', () => {
|
|
|
182
182
|
},
|
|
183
183
|
];
|
|
184
184
|
|
|
185
|
-
expect(el.primaryTitle).to.equal(
|
|
186
|
-
'Renewable every hour, pending availability.'
|
|
187
|
-
);
|
|
185
|
+
expect(el.primaryTitle).to.equal('');
|
|
188
186
|
expect(el.primaryActions.length).to.equal(2);
|
|
189
187
|
expect(el.primaryActions[0].text).to.equal('Log In and Borrow');
|
|
190
188
|
expect(el.primaryActions[0].text).to.equal(expectedPrimaryActions[0].text);
|
|
@@ -253,7 +251,6 @@ describe('Browsing expired status', () => {
|
|
|
253
251
|
await aTimeout(1500); // wait for 1.5 second
|
|
254
252
|
await el.updateComplete;
|
|
255
253
|
|
|
256
|
-
expect(el.primaryTitle).contains('Your loan has expired.');
|
|
257
254
|
expect(el.primaryActions[0].text).to.equal('Borrow again');
|
|
258
255
|
});
|
|
259
256
|
|
|
@@ -289,7 +286,6 @@ describe('Browsing expired status', () => {
|
|
|
289
286
|
await aTimeout(1500); // wait for 1.5 sec
|
|
290
287
|
|
|
291
288
|
expect(eventReceived).to.equal(true);
|
|
292
|
-
expect(el.primaryTitle).contains('Your loan has expired.');
|
|
293
289
|
expect(el.primaryActions[0].text).to.equal('Borrow again');
|
|
294
290
|
expect(el.tokenPoller.loanTokenInterval).to.equal(undefined);
|
|
295
291
|
});
|
|
@@ -1,148 +0,0 @@
|
|
|
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
|
-
}
|