@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 +27 -2
- package/package.json +7 -2
- package/src/assets/data/lending-get-status-service.js +1 -1
- package/src/components/collapsible-action-group.js +17 -0
- package/src/components/info-icon.js +1 -1
- package/src/components/timer-countdown.js +114 -0
- package/src/components/toast-message.js +80 -0
- package/src/core/services/actions-handler/actions-handler.js +40 -2
- package/src/core/services/one-hour-auto-renew.js +157 -0
- package/src/ia-book-actions.js +130 -6
- package/demo/with-bookreader.html +0 -146
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 =
|
|
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.
|
|
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:
|
|
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
|
/**
|
|
@@ -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
|
+
}
|
package/src/ia-book-actions.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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>
|