@internetarchive/ia-book-actions 0.0.29-alpha4 → 0.0.29-alpha40

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/demo/index.html CHANGED
@@ -15,7 +15,7 @@
15
15
  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
16
16
  }
17
17
  #demo {
18
- height: 50px;
18
+ height: 100px;
19
19
  }
20
20
  ia-book-actions {
21
21
  --white: #fff;
@@ -72,6 +72,10 @@
72
72
  margin: 2px;
73
73
  }
74
74
 
75
+ fieldset {
76
+ display: inline-grid;
77
+ }
78
+
75
79
  body.modal-manager-open {
76
80
  overflow: hidden;
77
81
  }
@@ -87,6 +91,7 @@
87
91
 
88
92
  <script type="module">
89
93
  /* eslint-disable */
94
+
90
95
  import '../src/ia-book-actions.js';
91
96
  import {
92
97
  defaultLendingStatus,
@@ -96,7 +101,7 @@
96
101
  import { LocalCache } from '@internetarchive/local-cache';
97
102
 
98
103
  const localCache = new LocalCache({
99
- namespace: '1HourLoanStorage',
104
+ namespace: 'loanRenew',
100
105
  defaultTTL: 1 * 60, // 1 minute
101
106
  });
102
107
 
@@ -104,10 +109,6 @@
104
109
  const identifier = 'naturalhistoryof00unse_4111';
105
110
  let bwbPurchaseUrl = 'https://www.betterworldbooks.com/product/detail/cambridge-ancient-hist-v04-0521044863';
106
111
 
107
- let iaBookActions = document.querySelector('ia-book-actions');
108
-
109
- // for debugging
110
- window.iaBookActions = iaBookActions;
111
112
 
112
113
  const modalConfig = new ModalConfig();
113
114
  modalConfig.headerColor = '#d9534f';
@@ -117,6 +118,9 @@
117
118
  }
118
119
 
119
120
  // set defaultLendingStatus for unavailable (without borrowables) book
121
+ let iaBookActions = document.querySelector('ia-book-actions');
122
+
123
+
120
124
  iaBookActions.userid = userid;
121
125
  iaBookActions.identifier = identifier;
122
126
  iaBookActions.bookTitle = 'Contemporary Black biography. Volume 39 : profiles from the interContemporary Black biography. Volume 39';
@@ -124,17 +128,20 @@
124
128
  iaBookActions.bwbPurchaseUrl = '';
125
129
  iaBookActions.modalConfig = modalConfig;
126
130
  iaBookActions.lendingBarPostInit = lendingBarPostInit;
127
- iaBookActions.tokenDelay = 2000; // 2 seconds
131
+ iaBookActions.tokenDelay = 2; // 2 seconds
128
132
  iaBookActions.returnUrl = 'https://openlibrary.org';
129
133
  iaBookActions.localCache = localCache;
130
- iaBookActions.autoRenewConfig = {
131
- totalTime: 10, // 60 second = 1 min
132
- autoCheckAt: 8, // totalTime - 10th minute
133
- FlippedInLast: 15, // within last 15 second/minute
134
+
135
+ let isDevBox = true;
136
+ iaBookActions.loanRenewTimeConfig = {
137
+ loanTotalTime: isDevBox ? 13 : 3600, // 60 second = 1 min
138
+ loanRenewAtLast: isDevBox ? 10 : 3540, // at last 10th minute
139
+ pageChangedInLast: isDevBox ? 5 : 900, // within last 15 second/minute
140
+ isDevBox: isDevBox,
134
141
  };
135
142
 
136
143
  let lendingStatus1 = iaBookActions.lendingStatus;
137
- let secondsLeftOnLoan = iaBookActions.autoRenewConfig.totalTime;
144
+ let secondsLeftOnLoan = iaBookActions.loanRenewTimeConfig.loanTotalTime;
138
145
 
139
146
  document.querySelectorAll('.titleBar input[type=checkbox]').forEach((item) => {
140
147
  item.addEventListener('click', (e) => {
@@ -146,21 +153,6 @@
146
153
  });
147
154
  });
148
155
 
149
- document.querySelectorAll('.autoRenew input[type=checkbox]').forEach((item) => {
150
- item.addEventListener('click', (e) => {
151
- if (e.target.checked) {
152
- iaBookActions.autoRenew = true;
153
- iaBookActions.resetTimerCountdown = true;
154
- lendingStatus1.secondsLeftOnLoan = secondsLeftOnLoan;
155
- } else {
156
- iaBookActions.autoRenew = false;
157
- }
158
-
159
- let lending = {...defaultLendingStatus, ...lendingStatus1};
160
- iaBookActions.lendingStatus = lending;
161
- });
162
- });
163
-
164
156
  document.querySelectorAll('.errorEnable input[type=checkbox]').forEach((item) => {
165
157
  if (window.location.href.indexOf('?error=true') !== -1) {
166
158
  item.checked = true;
@@ -226,10 +218,10 @@
226
218
  await localCache.set({
227
219
  key: `${identifier}-loanTime`,
228
220
  value: new Date(), // current time
229
- ttl: parseInt(iaBookActions.autoRenewConfig.totalTime),
230
- })
221
+ ttl: Number(iaBookActions.loanRenewTimeConfig.loanTotalTime),
222
+ });
231
223
 
232
- await localCache.delete(`${identifier}-pageFlipTime`);
224
+ await localCache.delete(`${identifier}-pageChangedTime`);
233
225
  } catch (error) {}
234
226
 
235
227
  } else if(e.target.value === 'browsingExpired') {
@@ -242,8 +234,11 @@
242
234
  lendingStatus1.user_has_browsed = false;
243
235
  }
244
236
 
245
- let lending = {...defaultLendingStatus, ...lendingStatus1};
246
- iaBookActions.lendingStatus = lending;
237
+ // wait for a while
238
+ setTimeout(() => {
239
+ let lending = {...defaultLendingStatus, ...lendingStatus1};
240
+ iaBookActions.lendingStatus = lending;
241
+ }, 10);
247
242
  });
248
243
  });
249
244
 
@@ -292,6 +287,17 @@
292
287
  iaBookActions.bwbPurchaseUrl = bwbPurchaseUrl;
293
288
  })
294
289
  });
290
+
291
+ document.querySelector('.pageChangedEvent').addEventListener('click', () => {
292
+ // dispatch event to say book page has flipped
293
+ document.querySelector('ia-book-actions').dispatchEvent(
294
+ new CustomEvent('BookReader:userAction', {
295
+ detail: {},
296
+ bubbles: true,
297
+ composed: true,
298
+ })
299
+ );
300
+ })
295
301
  </script>
296
302
 
297
303
  <div>
@@ -302,6 +308,9 @@
302
308
  <td class="titleBar">
303
309
  <label><input type="checkbox" name="title" value="title" />Title Bar</label>
304
310
  </td>
311
+ <td>
312
+ <button class="pageChangedEvent">Page Change Event</button>
313
+ </td>
305
314
  </tr>
306
315
  </table>
307
316
  </fieldset>
@@ -311,7 +320,6 @@
311
320
  <table class="initial">
312
321
  <tr colspan='5'>
313
322
  <td class="errorEnable"><label><input type="checkbox" name="errorEnable" value="errorEnable" />Do you want to see error response?</label></td>
314
- <td class="autoRenew"><label><input type="checkbox" name="autoRenew" value="autoRenew" />Do you want to auto-renew 1 hour</label></td>
315
323
  </tr>
316
324
  <tr>
317
325
  <td class="userState">
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@internetarchive/ia-book-actions",
3
- "version": "0.0.29-alpha4",
3
+ "version": "0.0.29-alpha40",
4
4
  "description": "Bookreader actions for borrowable book",
5
5
  "author": "Internet Archive",
6
6
  "license": "AGPL-3.0-only",
@@ -11,8 +11,8 @@
11
11
  },
12
12
  "scripts": {
13
13
  "start": "web-dev-server",
14
- "lint": "eslint --ext .js",
15
- "format": "eslint --ext .js,",
14
+ "lint": "eslint --ext .js,.html . --ignore-path .gitignore && prettier \"**/*.js\" --check --ignore-path .gitignore",
15
+ "format": "eslint --ext .js,.html . --fix --ignore-path .gitignore && prettier \"**/*.js\" --write --ignore-path .gitignore",
16
16
  "test": "yarn run lint && yarn test:fast",
17
17
  "test:fast": "web-test-runner --coverage --playwright --browsers chromium firefox webkit",
18
18
  "test:watch": "web-test-runner --watch --playwright --browsers chromium firefox webkit"
@@ -20,9 +20,10 @@
20
20
  "dependencies": {
21
21
  "@internetarchive/icon-info": "^1.2.6",
22
22
  "@internetarchive/local-cache": "^0.2.0",
23
- "@internetarchive/modal-manager": "^0.2.1",
23
+ "@internetarchive/modal-manager": "^0.2.8",
24
24
  "@internetarchive/shared-resize-observer": "^0.2.0",
25
- "lit": "^2.2.1"
25
+ "@internetarchive/toast-manager": "0.1.0",
26
+ "lit": "^2.5.0"
26
27
  },
27
28
  "devDependencies": {
28
29
  "@open-wc/eslint-config": "^4.2.0",
@@ -43,6 +44,12 @@
43
44
  "sinon": "^13.0.2"
44
45
  },
45
46
  "eslintConfig": {
47
+ "rules": {
48
+ "no-console": "warn",
49
+ "no-unused-expressions": "warn",
50
+ "class-methods-use-this": "warn",
51
+ "lit-a11y/click-events-have-key-events": "warn"
52
+ },
46
53
  "extends": [
47
54
  "@open-wc/eslint-config",
48
55
  "eslint-config-prettier"
@@ -54,6 +61,7 @@
54
61
  },
55
62
  "husky": {
56
63
  "hooks": {
64
+ "pre-commit": "lint-staged"
57
65
  }
58
66
  },
59
67
  "lint-staged": {
@@ -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;
@@ -9,6 +9,10 @@ import CollapsibleActionGroupStyle from '../assets/styles/collapsible-action-gro
9
9
 
10
10
  import { tabletContainerWidth } from '../core/config/constants.js';
11
11
  import { purchaseIcon } from '../assets/data/purchase.js';
12
+ import {
13
+ analyticsCategories,
14
+ analyticsActions,
15
+ } from '../core/config/analytics-event-and-category.js';
12
16
  import {
13
17
  dropdownOpened,
14
18
  dropdownClosed,
@@ -28,8 +32,7 @@ export class CollapsibleActionGroup extends ActionsHandler {
28
32
  dropdownArrow: { type: String },
29
33
  disabled: { type: Boolean },
30
34
  returnUrl: { type: String },
31
- browseAgainNow: { type: Boolean },
32
- autoRenewConfig: { type: Object },
35
+ renewNow: { type: Boolean },
33
36
  };
34
37
  }
35
38
 
@@ -49,8 +52,7 @@ export class CollapsibleActionGroup extends ActionsHandler {
49
52
  this.loaderIcon = 'https://archive.org/upload/images/tree/loading.gif';
50
53
  this.disabled = false;
51
54
  this.returnUrl = '';
52
- this.browseAgainNow = false;
53
- this.autoRenewConfig = {};
55
+ this.renewNow = false;
54
56
  }
55
57
 
56
58
  updated(changed) {
@@ -61,17 +63,28 @@ export class CollapsibleActionGroup extends ActionsHandler {
61
63
  this.resetActions();
62
64
  }
63
65
 
64
- if (this.browseAgainNow === true) {
65
- this.dispatchEvent(
66
- new CustomEvent('autoBrowseBook', {
67
- detail: {
68
- event: { 'autoRenewed1Hour': 'browseBook' },
69
- },
70
- })
71
- );
66
+ if (changed.has('renewNow') && this.renewNow === true) {
67
+ this.dispatchLoanRenewEvent();
72
68
  }
73
69
  }
74
70
 
71
+ // dispatch this event to loan renew handler
72
+ dispatchLoanRenewEvent() {
73
+ const { category, action } = {
74
+ category: analyticsCategories.browse,
75
+ action: analyticsActions.browseLoanRenew,
76
+ };
77
+
78
+ // listen in action-handler.js to execute ajax call on petabox.
79
+ this.dispatchEvent(
80
+ new CustomEvent('loanRenew', {
81
+ detail: {
82
+ event: { category, action },
83
+ },
84
+ })
85
+ );
86
+ }
87
+
75
88
  /**
76
89
  * merge primaryActions and secondaryActions into dropdown
77
90
  */
@@ -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
  }
@@ -26,7 +26,7 @@ export class TextGroup extends LitElement {
26
26
  display: inline-block;
27
27
  }
28
28
  .variable-texts {
29
- margin-left: 10px;
29
+ margin-right: 10px;
30
30
  vertical-align: middle;
31
31
  font-size: 1.7rem;
32
32
  }
@@ -1,48 +1,68 @@
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
- time: { type: Number },
7
- autoCheckAt: { type: Number },
8
- resetTimerCountdown: { type: Boolean },
7
+ secondsLeftOnLoan: { type: Number }, // in seconds
8
+ loanRenewAtLast: { type: Number }, // in seconds
9
+ isDevBox: { type: Boolean },
9
10
  };
10
11
  }
11
12
 
12
13
  constructor() {
13
14
  super();
14
- this.time = 60;
15
- this.autoCheckAt = 0;
15
+
16
+ /**
17
+ * seconds left in current time
18
+ * @type {number}
19
+ */
20
+ this.secondsLeftOnLoan = 0;
21
+
22
+ /**
23
+ * at the remaining time, we attempt to renew current loan
24
+ * @type {number}
25
+ */
26
+ this.loanRenewAtLast = 0;
27
+
28
+ /**
29
+ * @type {boolean}
30
+ */
31
+ this.isDevBox = false;
32
+
33
+ // private props
16
34
  this.timerInterval = undefined;
17
- this.resetTimerCountdown = false;
18
35
  }
19
36
 
20
37
  disconnectedCallback() {
21
- clearInterval(this.timerInterval)
22
- }
23
-
24
- firstUpdated() {
25
- this.timerCountdown();
38
+ clearInterval(this.timerInterval);
26
39
  }
27
40
 
28
41
  updated(changed) {
29
- if (changed.has('resetTimerCountdown')) {
42
+ if (changed.has('secondsLeftOnLoan') && this.secondsLeftOnLoan > 0) {
30
43
  clearInterval(this.timerInterval);
31
- this.timerCountdown()
44
+ this.timerCountdown();
32
45
  }
33
46
  }
34
-
47
+
35
48
  timerCountdown() {
49
+ // in-case of isDevBox, execute timer in each second instead of 60
50
+ const timerIntervalSeconds = this.isDevBox ? 1 : 60;
51
+
36
52
  this.timerInterval = setInterval(() => {
37
- this.time -= 1;
53
+ // if this.isDevBox, just reduce seconds by 1 instead of 60 (1 min)
54
+ this.secondsLeftOnLoan -= timerIntervalSeconds;
55
+ const secondsLeft = Math.round(this.secondsLeftOnLoan);
38
56
 
39
- // execute 50th minute check
40
- if (this.time === this.autoCheckAt) {
57
+ // execute from last 10th minute to 0th minute
58
+ // - 10th - to check if user has viewed
59
+ // - till 0th - to show warning msg with remaining time to auto expired
60
+ if (secondsLeft <= this.loanRenewAtLast) {
41
61
  this.dispatchEvent(
42
- new CustomEvent('autoRenewAttempt', {
62
+ new CustomEvent('IABookActions:loanRenew', {
43
63
  detail: {
44
- remainingT: this.time,
45
- bookPageChange: false,
64
+ hasPageChanged: false,
65
+ secondsLeft,
46
66
  },
47
67
  bubbles: true,
48
68
  composed: true,
@@ -50,76 +70,66 @@ export default class TimerCountdown extends LitElement {
50
70
  );
51
71
  }
52
72
 
53
- if (this.time === 0) clearInterval(this.timerInterval);
54
-
55
- }, 1000);
73
+ // clear interval
74
+ if (secondsLeft <= 1) {
75
+ clearInterval(this.timerInterval);
76
+ window?.Sentry?.captureMessage(sentryLogs.clearOneHourTimer);
77
+ console.log(sentryLogs.clearOneHourTimer);
78
+ }
79
+ }, timerIntervalSeconds * 1000);
56
80
  }
57
81
 
58
-
59
82
  render() {
60
- return html`<div id="countdown">
61
- <div id="countdown-number">${this.time}</div>
62
- <svg>
63
- <circle r="18" cx="20" cy="20"></circle>
64
- </svg>
65
- </div>`
83
+ return html`
84
+ <svg
85
+ width="16"
86
+ height="16"
87
+ viewBox="0 0 100 100"
88
+ xmlns="http://www.w3.org/2000/svg"
89
+ >
90
+ <circle class="circle" cx="50" cy="50" r="50" />
91
+ </svg>
92
+ <span class="sr-only"></span>
93
+ <span>${Math.round(this.secondsLeftOnLoan)}</span>
94
+ `;
66
95
  }
67
96
 
68
97
  static get styles() {
98
+ const white = css`var(--white, #fff)`;
99
+ const timerSecondsLeft = css`var(--secondsLeftOnLoan, 6000s)`; //
100
+ const timerStrokeLeft = css`var(--strokeLeftOnLoan, 315)`;
101
+
69
102
  return css`
70
103
  :host {
71
104
  right: 5px;
72
- font-size: 14px;
73
- position: absolute;
74
- }
75
- #countdown1 {
76
- position: relative;
77
- margin: auto;
78
- margin-top: 100px;
79
- height: 40px;
80
- width: 40px;
81
- text-align: center;
82
- }
83
-
84
- #countdown-number {
85
- color: white;
86
- display: inline-block;
87
- line-height: 40px;
88
- margin-right: 12px;
89
- }
90
-
91
- svg {
92
105
  position: absolute;
93
- top: 0;
94
- right: 0;
95
- width: 40px;
96
- height: 40px;
97
- transform: rotateY(-180deg) rotateZ(-90deg);
98
106
  }
99
-
100
- svg circle {
101
- stroke-dasharray: 113px;
102
- stroke-dashoffset: 0px;
103
- stroke-linecap: round;
104
- stroke-width: 3px;
105
- stroke: white;
106
- fill: none;
107
- animation: countdown 60s linear infinite forwards;
108
- }
109
-
110
- @keyframes countdown {
111
- from {
112
- stroke-dashoffset: 0px;
107
+
108
+ @keyframes circletimer {
109
+ 0% {
110
+ stroke-dashoffset: ${timerStrokeLeft};
111
+ stroke-dasharray: 315;
113
112
  }
114
- to {
115
- stroke-dashoffset: 113px;
113
+ 100% {
114
+ stroke-dashoffset: 0;
115
+ stroke-dasharray: 315;
116
116
  }
117
117
  }
118
- .hidden {
119
- display: none;
118
+
119
+ svg {
120
+ background-color: ${white};
121
+ border-radius: 50px;
122
+ border: 2px solid ${white};
123
+ transform: rotateZ(-90deg);
120
124
  }
121
- .visible {
122
- display: inline-block;
125
+
126
+ svg .circle {
127
+ stroke: #000;
128
+ stroke-width: 100px;
129
+ fill: ${white};
130
+ stroke-dashoffset: 315;
131
+ stroke-dasharray: 0;
132
+ animation: ${timerSecondsLeft} circletimer linear;
123
133
  }
124
134
  `;
125
135
  }
@@ -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 for 1 hour',
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 again',
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 (err) {
68
- window?.Sentry?.captureException(err);
68
+ } catch (error) {
69
+ window?.Sentry?.captureException(
70
+ `${sentryLogs.actionsHandlerService} - Error: ${error}`
71
+ );
69
72
  }
70
73
  }