@internetarchive/ia-book-actions 0.0.29-alpha9 → 0.1.0
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 +39 -34
- package/demo/with-bookreader.html +146 -0
- package/package.json +6 -10
- package/src/assets/data/lending-get-status-service.js +1 -1
- package/src/assets/styles/collapsible-action-group.js +2 -1
- package/src/components/collapsible-action-group.js +25 -12
- package/src/components/info-icon.js +0 -9
- package/src/components/text-group.js +1 -1
- package/src/components/timer-countdown.js +118 -53
- package/src/core/config/analytics-event-and-category.js +1 -0
- package/src/core/config/sentry-events.js +20 -0
- package/src/core/services/actions-config.js +2 -2
- package/src/core/services/actions-handler/actions-handler-service.js +5 -2
- package/src/core/services/actions-handler/actions-handler.js +36 -18
- package/src/core/services/get-lending-actions.js +6 -11
- package/src/core/services/loan-renew-helper.js +161 -0
- package/src/core/services/loan-token-poller.js +19 -10
- package/src/ia-book-actions.js +323 -147
- package/test/components/collapsible-action-group.test.js +2 -2
- package/test/components/timer-countdown.test.js +45 -0
- package/test/core/services/get-lending-actions.test.js +4 -4
- package/test/core/services/loan-renew-helper.test.js +86 -0
- package/test/ia-book-actions.test.js +75 -12
- package/src/components/toast-message.js +0 -78
- package/src/core/services/one-hour-auto-renew.js +0 -169
|
@@ -1,20 +1,47 @@
|
|
|
1
1
|
import { html, css, LitElement } from 'lit';
|
|
2
|
+
import { sentryLogs } from '../core/config/sentry-events.js';
|
|
2
3
|
|
|
3
4
|
export default class TimerCountdown extends LitElement {
|
|
4
5
|
static get properties() {
|
|
5
6
|
return {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
secondsLeftOnLoan: { type: Number }, // in seconds
|
|
8
|
+
loanTotalTime: { type: Number }, // in seconds
|
|
9
|
+
loanRenewAtLast: { type: Number }, // in seconds
|
|
9
10
|
};
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
constructor() {
|
|
13
14
|
super();
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* seconds left in current loan
|
|
18
|
+
* @type {number}
|
|
19
|
+
*/
|
|
20
|
+
this.secondsLeftOnLoan = 0;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* total seconds a loan does have
|
|
24
|
+
* @type {number}
|
|
25
|
+
*/
|
|
26
|
+
this.loanTotalTime = 0;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* at the remaining time, we attempt to renew current loan
|
|
30
|
+
* @type {number}
|
|
31
|
+
*/
|
|
32
|
+
this.loanRenewAtLast = 0;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* setInterval executed interval
|
|
36
|
+
* @type {number}
|
|
37
|
+
*/
|
|
16
38
|
this.timerInterval = undefined;
|
|
17
|
-
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* delay seconds in setInterval function
|
|
42
|
+
* @type {number}
|
|
43
|
+
*/
|
|
44
|
+
this.timerExecutionSeconds = 0;
|
|
18
45
|
}
|
|
19
46
|
|
|
20
47
|
disconnectedCallback() {
|
|
@@ -22,23 +49,32 @@ export default class TimerCountdown extends LitElement {
|
|
|
22
49
|
}
|
|
23
50
|
|
|
24
51
|
updated(changed) {
|
|
25
|
-
if (changed.has('
|
|
52
|
+
if (changed.has('secondsLeftOnLoan') && this.secondsLeftOnLoan > 0) {
|
|
26
53
|
clearInterval(this.timerInterval);
|
|
27
54
|
this.timerCountdown();
|
|
28
55
|
}
|
|
29
56
|
}
|
|
30
57
|
|
|
31
58
|
timerCountdown() {
|
|
59
|
+
// - let just execute setInterval in every 1 minute
|
|
60
|
+
this.timerExecutionSeconds = 60;
|
|
61
|
+
|
|
32
62
|
this.timerInterval = setInterval(() => {
|
|
33
|
-
this.
|
|
63
|
+
this.secondsLeftOnLoan -= this.timerExecutionSeconds;
|
|
64
|
+
const secondsLeft = Math.round(this.secondsLeftOnLoan);
|
|
34
65
|
|
|
35
|
-
|
|
36
|
-
|
|
66
|
+
/**
|
|
67
|
+
* execute from last 10th minute to 0th minute
|
|
68
|
+
* - 10th - to check if user has viewed
|
|
69
|
+
* - till 0th - to show warning msg with remaining time to auto expired
|
|
70
|
+
* @see IABookActions::bindLoanRenewEvents
|
|
71
|
+
*/
|
|
72
|
+
if (secondsLeft <= this.loanRenewAtLast) {
|
|
37
73
|
this.dispatchEvent(
|
|
38
|
-
new CustomEvent('
|
|
74
|
+
new CustomEvent('IABookActions:loanRenew', {
|
|
39
75
|
detail: {
|
|
40
|
-
|
|
41
|
-
|
|
76
|
+
hasPageChanged: false,
|
|
77
|
+
secondsLeft,
|
|
42
78
|
},
|
|
43
79
|
bubbles: true,
|
|
44
80
|
composed: true,
|
|
@@ -46,65 +82,94 @@ export default class TimerCountdown extends LitElement {
|
|
|
46
82
|
);
|
|
47
83
|
}
|
|
48
84
|
|
|
49
|
-
|
|
50
|
-
|
|
85
|
+
// clear interval
|
|
86
|
+
if (secondsLeft <= 60) {
|
|
87
|
+
clearInterval(this.timerInterval);
|
|
88
|
+
window?.Sentry?.captureMessage(sentryLogs.clearOneHourTimer);
|
|
89
|
+
}
|
|
90
|
+
}, this.timerExecutionSeconds * 1000);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* get remaining time with timeunit
|
|
95
|
+
*
|
|
96
|
+
* @memberof TimerCountdown
|
|
97
|
+
* @return string
|
|
98
|
+
*/
|
|
99
|
+
get remainingTime() {
|
|
100
|
+
const unitOfTime = 'minute';
|
|
101
|
+
let timeLeft = Math.round(this.secondsLeftOnLoan);
|
|
102
|
+
|
|
103
|
+
// convert time from second to minute
|
|
104
|
+
timeLeft = Math.ceil(timeLeft / 60);
|
|
105
|
+
|
|
106
|
+
return timeLeft !== 1
|
|
107
|
+
? `${timeLeft} ${unitOfTime}s`
|
|
108
|
+
: `${timeLeft} ${unitOfTime}`;
|
|
51
109
|
}
|
|
52
110
|
|
|
53
111
|
render() {
|
|
54
|
-
return html
|
|
55
|
-
<
|
|
56
|
-
|
|
57
|
-
|
|
112
|
+
return html`
|
|
113
|
+
<svg
|
|
114
|
+
width="16"
|
|
115
|
+
height="16"
|
|
116
|
+
viewBox="0 0 100 100"
|
|
117
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
118
|
+
>
|
|
119
|
+
<circle class="circle" cx="50" cy="50" r="50" />
|
|
58
120
|
</svg>
|
|
59
|
-
|
|
121
|
+
<span class="sr-only">${this.remainingTime}</span>
|
|
122
|
+
<span>${this.remainingTime}</span>
|
|
123
|
+
`;
|
|
60
124
|
}
|
|
61
125
|
|
|
62
126
|
static get styles() {
|
|
127
|
+
const white = css`var(--white, #fff)`;
|
|
128
|
+
const timerSecondsLeft = css`var(--secondsLeftOnLoan, 6000s)`; //
|
|
129
|
+
const timerStrokeLeft = css`var(--strokeLeftOnLoan, 315)`;
|
|
130
|
+
|
|
63
131
|
return css`
|
|
64
132
|
:host {
|
|
65
133
|
right: 5px;
|
|
66
|
-
font-size: 14px;
|
|
67
134
|
position: absolute;
|
|
68
135
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
136
|
+
|
|
137
|
+
@keyframes circletimer {
|
|
138
|
+
0% {
|
|
139
|
+
stroke-dashoffset: ${timerStrokeLeft};
|
|
140
|
+
stroke-dasharray: 315;
|
|
141
|
+
}
|
|
142
|
+
100% {
|
|
143
|
+
stroke-dashoffset: 0;
|
|
144
|
+
stroke-dasharray: 315;
|
|
145
|
+
}
|
|
74
146
|
}
|
|
75
147
|
|
|
76
148
|
svg {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
height: 40px;
|
|
82
|
-
transform: rotateY(-180deg) rotateZ(-90deg);
|
|
149
|
+
background-color: ${white};
|
|
150
|
+
border-radius: 50px;
|
|
151
|
+
border: 2px solid ${white};
|
|
152
|
+
transform: rotateZ(-90deg);
|
|
83
153
|
}
|
|
84
154
|
|
|
85
|
-
svg circle {
|
|
86
|
-
stroke
|
|
87
|
-
stroke-
|
|
88
|
-
|
|
89
|
-
stroke-
|
|
90
|
-
stroke:
|
|
91
|
-
|
|
92
|
-
animation: countdown 60s linear infinite forwards;
|
|
155
|
+
svg .circle {
|
|
156
|
+
stroke: #000;
|
|
157
|
+
stroke-width: 100px;
|
|
158
|
+
fill: ${white};
|
|
159
|
+
stroke-dashoffset: 315;
|
|
160
|
+
stroke-dasharray: 0;
|
|
161
|
+
animation: ${timerSecondsLeft} circletimer linear;
|
|
93
162
|
}
|
|
94
163
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
display: none;
|
|
105
|
-
}
|
|
106
|
-
.visible {
|
|
107
|
-
display: inline-block;
|
|
164
|
+
.sr-only {
|
|
165
|
+
position: absolute;
|
|
166
|
+
left: -9999px;
|
|
167
|
+
width: 1px;
|
|
168
|
+
height: 1px;
|
|
169
|
+
margin: 0;
|
|
170
|
+
padding: 0;
|
|
171
|
+
border: none;
|
|
172
|
+
overflow: hidden;
|
|
108
173
|
}
|
|
109
174
|
`;
|
|
110
175
|
}
|
|
@@ -13,6 +13,7 @@ export const analyticsCategories = {
|
|
|
13
13
|
export const analyticsActions = {
|
|
14
14
|
browse: 'Borrow-1Hour',
|
|
15
15
|
browseAgain: 'Borrow-Again',
|
|
16
|
+
browseLoanRenew: 'Borrow-Loan-Renew',
|
|
16
17
|
borrow: 'Borrow-14Days',
|
|
17
18
|
waitlistJoin: 'JoinWaitlist',
|
|
18
19
|
waitlistLeave: 'LeaveWaitlist',
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* sentry logs/messages we send for different actions....
|
|
3
|
+
*/
|
|
4
|
+
export const sentryLogs = {
|
|
5
|
+
disconnectedCallback: 'IABookActions:disconnectedCallback',
|
|
6
|
+
bookHasRenewed:
|
|
7
|
+
'IABookActions:handleLoanAutoRenewed - book has renewed for next one hour',
|
|
8
|
+
browseHasExpired:
|
|
9
|
+
'IABookActions:browseHasExpired - one-hour loan has been expired',
|
|
10
|
+
bookWasExpired:
|
|
11
|
+
'IABookActions:setupLendingToolbarActions - book was expired at intial, no tokenPoller',
|
|
12
|
+
clearTokenPoller:
|
|
13
|
+
'IABookActions:startLoanTokenPoller - clearing token poller interval',
|
|
14
|
+
clearOneHourTimer:
|
|
15
|
+
'IABookActions:timerCountdown - one-hour timer interval cleared',
|
|
16
|
+
bookAccessed: 'IABookActions:bookAccessed',
|
|
17
|
+
handleLoanTokenPoller: 'IABookActions:handleLoanTokenPoller',
|
|
18
|
+
setConsecutiveLoanCounts: 'IABookActions:setConsecutiveLoanCounts',
|
|
19
|
+
actionsHandlerService: 'IABookActions:actionsHandlerService',
|
|
20
|
+
};
|
|
@@ -36,7 +36,7 @@ export default class ActionsConfig {
|
|
|
36
36
|
firstBrowseConfig() {
|
|
37
37
|
return {
|
|
38
38
|
id: 'browseBook',
|
|
39
|
-
text: 'Borrow
|
|
39
|
+
text: 'Borrow',
|
|
40
40
|
className: 'primary',
|
|
41
41
|
analyticsEvent: {
|
|
42
42
|
category: this.analyticsCategories.preview,
|
|
@@ -48,7 +48,7 @@ export default class ActionsConfig {
|
|
|
48
48
|
browseAgainConfig() {
|
|
49
49
|
return {
|
|
50
50
|
id: 'browseBookAgain',
|
|
51
|
-
text: 'Borrow
|
|
51
|
+
text: 'Borrow',
|
|
52
52
|
className: 'primary',
|
|
53
53
|
analyticsEvent: {
|
|
54
54
|
category: this.analyticsCategories.browse,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
/* eslint-disable */
|
|
2
|
+
import { sentryLogs } from '../../config/sentry-events.js';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Helper to call loan service
|
|
@@ -64,7 +65,9 @@ export default async function ActionsHandlerService(options) {
|
|
|
64
65
|
option?.error(data);
|
|
65
66
|
}
|
|
66
67
|
});
|
|
67
|
-
} catch (
|
|
68
|
-
window?.Sentry?.captureException(
|
|
68
|
+
} catch (error) {
|
|
69
|
+
window?.Sentry?.captureException(
|
|
70
|
+
`${sentryLogs.actionsHandlerService} - Error: ${error}`
|
|
71
|
+
);
|
|
69
72
|
}
|
|
70
73
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { LitElement } from 'lit';
|
|
2
2
|
|
|
3
3
|
import { URLHelper } from '../../config/url-helper.js';
|
|
4
|
+
import { sentryLogs } from '../../config/sentry-events.js';
|
|
5
|
+
|
|
4
6
|
import ActionsHandlerService from './actions-handler-service.js';
|
|
5
7
|
import * as Cookies from '../doc-cookies.js';
|
|
6
8
|
|
|
@@ -15,10 +17,13 @@ import * as Cookies from '../doc-cookies.js';
|
|
|
15
17
|
export default class ActionsHandler extends LitElement {
|
|
16
18
|
constructor() {
|
|
17
19
|
super();
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* wait untill borrow is complete, then refresh the page
|
|
23
|
+
* @type {number}
|
|
24
|
+
*/
|
|
25
|
+
this.waitUntillBorrowComplete = 6; // in seconds
|
|
26
|
+
|
|
22
27
|
this.bindEvents();
|
|
23
28
|
}
|
|
24
29
|
|
|
@@ -30,7 +35,7 @@ export default class ActionsHandler extends LitElement {
|
|
|
30
35
|
);
|
|
31
36
|
}
|
|
32
37
|
|
|
33
|
-
|
|
38
|
+
bindEvents() {
|
|
34
39
|
this.addEventListener('browseBook', ({ detail }) => {
|
|
35
40
|
this.handleBrowseIt();
|
|
36
41
|
this.setConsecutiveLoanCounts();
|
|
@@ -45,9 +50,8 @@ export default class ActionsHandler extends LitElement {
|
|
|
45
50
|
this.sendEvent(category, action);
|
|
46
51
|
});
|
|
47
52
|
|
|
48
|
-
this.addEventListener('
|
|
49
|
-
|
|
50
|
-
this.handleAutoBrowseIt();
|
|
53
|
+
this.addEventListener('loanRenew', ({ detail }) => {
|
|
54
|
+
this.handleLoanRenew();
|
|
51
55
|
const { category, action } = detail.event;
|
|
52
56
|
this.sendEvent(category, action);
|
|
53
57
|
});
|
|
@@ -106,12 +110,12 @@ export default class ActionsHandler extends LitElement {
|
|
|
106
110
|
handleBrowseIt() {
|
|
107
111
|
const action = 'browse_book';
|
|
108
112
|
this.dispatchToggleActionGroup();
|
|
109
|
-
this.setBrowseTimeSession();
|
|
110
113
|
|
|
111
114
|
ActionsHandlerService({
|
|
112
115
|
action,
|
|
113
116
|
identifier: this.identifier,
|
|
114
117
|
success: () => {
|
|
118
|
+
this.setBrowseTimeSession();
|
|
115
119
|
this.handleReadItNow();
|
|
116
120
|
},
|
|
117
121
|
error: data => {
|
|
@@ -120,15 +124,19 @@ export default class ActionsHandler extends LitElement {
|
|
|
120
124
|
});
|
|
121
125
|
}
|
|
122
126
|
|
|
123
|
-
|
|
127
|
+
handleLoanRenew() {
|
|
124
128
|
const action = 'renew_loan';
|
|
125
|
-
this.setBrowseTimeSession();
|
|
126
129
|
|
|
127
130
|
ActionsHandlerService({
|
|
128
131
|
action,
|
|
129
132
|
identifier: this.identifier,
|
|
130
|
-
success:
|
|
131
|
-
|
|
133
|
+
success: data => {
|
|
134
|
+
this.setBrowseTimeSession();
|
|
135
|
+
this.dispatchEvent(
|
|
136
|
+
new CustomEvent('loanAutoRenewed', {
|
|
137
|
+
detail: { action, data },
|
|
138
|
+
})
|
|
139
|
+
);
|
|
132
140
|
},
|
|
133
141
|
error: data => {
|
|
134
142
|
this.dispatchActionError(action, data);
|
|
@@ -260,7 +268,7 @@ export default class ActionsHandler extends LitElement {
|
|
|
260
268
|
// redirection on details page after 5 seconds because borrowing book takes time to create item creation.
|
|
261
269
|
setTimeout(() => {
|
|
262
270
|
URLHelper.goToUrl(redirectTo, true);
|
|
263
|
-
}, this.
|
|
271
|
+
}, this.waitUntillBorrowComplete * 1000);
|
|
264
272
|
}
|
|
265
273
|
|
|
266
274
|
// save consecutive loan count for borrow
|
|
@@ -281,21 +289,31 @@ export default class ActionsHandler extends LitElement {
|
|
|
281
289
|
// set new value
|
|
282
290
|
Cookies.setItem(storageKey, newCount, date, '/');
|
|
283
291
|
} catch (error) {
|
|
284
|
-
window?.Sentry?.captureException(
|
|
292
|
+
window?.Sentry?.captureException(
|
|
293
|
+
`${sentryLogs.setConsecutiveLoanCounts} - Error: ${error}`
|
|
294
|
+
);
|
|
285
295
|
this.sendEvent('Cookies-Error-Actions', error);
|
|
286
296
|
}
|
|
287
297
|
}
|
|
288
298
|
|
|
299
|
+
/**
|
|
300
|
+
* set browse time in indexedDB
|
|
301
|
+
*/
|
|
289
302
|
async setBrowseTimeSession() {
|
|
290
303
|
try {
|
|
304
|
+
const expireDate = new Date(
|
|
305
|
+
new Date().getTime() + this.loanTotalTime * 1000
|
|
306
|
+
);
|
|
307
|
+
|
|
291
308
|
// set a value
|
|
292
309
|
await this.localCache.set({
|
|
293
310
|
key: `${this.identifier}-loanTime`,
|
|
294
|
-
value:
|
|
295
|
-
ttl: Number(this.
|
|
311
|
+
value: expireDate,
|
|
312
|
+
ttl: Number(this.loanTotalTime),
|
|
296
313
|
});
|
|
297
314
|
|
|
298
|
-
|
|
315
|
+
// delete pageChangedTime when book is auto renew at nth minute
|
|
316
|
+
await this.localCache.delete(`${this.identifier}-pageChangedTime`);
|
|
299
317
|
} catch (error) {
|
|
300
318
|
console.log(error);
|
|
301
319
|
}
|
|
@@ -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,161 @@
|
|
|
1
|
+
/* eslint-disable class-methods-use-this */
|
|
2
|
+
|
|
3
|
+
import { nothing } from 'lit';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* This class is used to determine if use is eligible for auto renew loan.
|
|
7
|
+
*/
|
|
8
|
+
export class LoanRenewHelper {
|
|
9
|
+
constructor(hasPageChanged, identifier, localCache, loanRenewTimeConfig) {
|
|
10
|
+
this.hasPageChanged = hasPageChanged;
|
|
11
|
+
this.identifier = identifier;
|
|
12
|
+
this.localCache = localCache;
|
|
13
|
+
this.loanRenewTimeConfig = loanRenewTimeConfig;
|
|
14
|
+
|
|
15
|
+
// messages for auto return machenism
|
|
16
|
+
this.loanRenewMessage = 'This book has been renewed for 1 hour.';
|
|
17
|
+
this.loanReturnWarning =
|
|
18
|
+
'This book will be automatically returned in #time #unitsOfTime unless you turn a page.';
|
|
19
|
+
|
|
20
|
+
// private props
|
|
21
|
+
this.result = {
|
|
22
|
+
texts: null,
|
|
23
|
+
renewNow: false,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
handleLoanRenew() {
|
|
28
|
+
try {
|
|
29
|
+
if (this.hasPageChanged) {
|
|
30
|
+
return this.pageChanged(); // user clicked on page
|
|
31
|
+
}
|
|
32
|
+
return this.autoChecker(); // auto checker at last 10th minute
|
|
33
|
+
} catch (error) {
|
|
34
|
+
console.log(error);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return nothing;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Trigger this function when user has browsed a book and change the post
|
|
42
|
+
* - every time user change the page, we set current time in indexedDB
|
|
43
|
+
* - also check if need to auto renew current loan
|
|
44
|
+
*
|
|
45
|
+
* @returns {this.result}
|
|
46
|
+
*/
|
|
47
|
+
async pageChanged() {
|
|
48
|
+
const {
|
|
49
|
+
loanTotalTime, // 60
|
|
50
|
+
loanRenewAtLast, // 50
|
|
51
|
+
} = this.loanRenewTimeConfig;
|
|
52
|
+
const currentTime = new Date();
|
|
53
|
+
const loanTime = await this.localCache.get(`${this.identifier}-loanTime`);
|
|
54
|
+
|
|
55
|
+
const lastTimeFrame = this.changeTime(
|
|
56
|
+
loanTime,
|
|
57
|
+
loanTotalTime - loanRenewAtLast,
|
|
58
|
+
'add'
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
// if user viewed new page in last 10 minutes, renew immediately
|
|
62
|
+
if (lastTimeFrame !== null && currentTime >= lastTimeFrame) {
|
|
63
|
+
this.result.texts = this.loanRenewMessage;
|
|
64
|
+
this.result.renewNow = true;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
this.setPageChangedTime();
|
|
68
|
+
return this.result;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Trigger this function when countdown hits at last 10th minute
|
|
73
|
+
* - if user is active, just renew the loan at last 10th minute
|
|
74
|
+
*
|
|
75
|
+
* @returns {this.result}
|
|
76
|
+
*/
|
|
77
|
+
async autoChecker() {
|
|
78
|
+
const { pageChangedInLast } = this.loanRenewTimeConfig;
|
|
79
|
+
const pageChangedTime = await this.localCache.get(
|
|
80
|
+
`${this.identifier}-pageChangedTime`
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
// in last 15 min if user make any activity.
|
|
84
|
+
const pageChangeTimeFrame = this.changeTime(
|
|
85
|
+
new Date(),
|
|
86
|
+
pageChangedInLast,
|
|
87
|
+
'sub'
|
|
88
|
+
); // 15 seconds
|
|
89
|
+
|
|
90
|
+
if (
|
|
91
|
+
pageChangedTime === undefined ||
|
|
92
|
+
pageChangedTime <= pageChangeTimeFrame
|
|
93
|
+
) {
|
|
94
|
+
this.result.texts = this.loanReturnWarning;
|
|
95
|
+
this.result.renewNow = false; // not viewed
|
|
96
|
+
} else if (pageChangedTime >= pageChangeTimeFrame) {
|
|
97
|
+
this.result.texts = '';
|
|
98
|
+
this.result.renewNow = true; // viewed in last time frame
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return this.result;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* set current time in indexedDB when user viewed a new page
|
|
106
|
+
*/
|
|
107
|
+
async setPageChangedTime() {
|
|
108
|
+
await this.localCache.set({
|
|
109
|
+
key: `${this.identifier}-pageChangedTime`,
|
|
110
|
+
value: new Date(), // current time
|
|
111
|
+
ttl: Number(this.loanRenewTimeConfig.loanTotalTime),
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Texts we want to show in toast template including remaining time
|
|
117
|
+
* - eg. 1 minute, 2 minutes
|
|
118
|
+
*
|
|
119
|
+
* @param {String} texts
|
|
120
|
+
* @param {Number} secondsLeft
|
|
121
|
+
*
|
|
122
|
+
* @returns {String} // texts will be appear in toast template
|
|
123
|
+
*/
|
|
124
|
+
getMessageTexts(texts, secondsLeft) {
|
|
125
|
+
const unitOfTime = 'minute';
|
|
126
|
+
|
|
127
|
+
let toastTexts = texts;
|
|
128
|
+
let timeLeft = secondsLeft;
|
|
129
|
+
|
|
130
|
+
// convert time from second to minute
|
|
131
|
+
timeLeft = Math.ceil(timeLeft / 60);
|
|
132
|
+
|
|
133
|
+
// replace #time variable with remaining time
|
|
134
|
+
toastTexts = toastTexts?.replace(/#time/, timeLeft);
|
|
135
|
+
|
|
136
|
+
// replace #unitsOfTime variable with minute/minutes
|
|
137
|
+
return toastTexts?.replace(
|
|
138
|
+
/#unitsOfTime/,
|
|
139
|
+
timeLeft !== 1 ? `${unitOfTime}s` : unitOfTime
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Helper function to get time difference
|
|
145
|
+
*
|
|
146
|
+
* @param {Date} date
|
|
147
|
+
* @param {Number} seconds
|
|
148
|
+
* @param {String} op - like sub, add, mul, sub
|
|
149
|
+
*
|
|
150
|
+
* @returns {Object} Date
|
|
151
|
+
*/
|
|
152
|
+
changeTime(date, seconds, op) {
|
|
153
|
+
if (date === undefined) return null;
|
|
154
|
+
|
|
155
|
+
if (op === 'sub') {
|
|
156
|
+
return new Date(date.getTime() - seconds * 1000);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return new Date(date.getTime() + seconds * 1000);
|
|
160
|
+
}
|
|
161
|
+
}
|