@internetarchive/ia-book-actions 0.0.7-0 → 0.0.7-11

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/README.md CHANGED
@@ -1,69 +1,115 @@
1
- [![Build Status](https://travis-ci.com/internetarchive/iaux-your-webcomponent.svg?branch=master)](https://travis-ci.com/internetarchive/iaux-your-webcomponent) [![codecov](https://codecov.io/gh/internetarchive/iaux-your-webcomponent/branch/master/graph/badge.svg)](https://codecov.io/gh/internetarchive/iaux-your-webcomponent)
1
+ # Component for lendable and embed items
2
2
 
3
- # Internet Archive Javascript WebComponent Template
3
+ The book action button component build on LitElement with support for custom content and light DOM elements.
4
4
 
5
- This is a base template for creating Javascript WebComponents. It is based off of the [Open WebComponents generator](https://open-wc.org/docs/development/generator/) with some IA-specific customizations and some development niceities.
6
-
7
- ## Usage
8
-
9
- 1. Click the "Use this Template" button in GitHub to create a new repository based on this one.
10
- 2. Clone your new repo and update the things below.
11
-
12
- ### Things to update in your copy
13
- 1. Remove this section
14
- 2. Search for the strings `your-webcomponent` and `YourWebComponent` and those are most of the spots that need to be updated.
15
- 3. `README.md` (this file). Update the readme in general, but also the badge URLs
16
- 4. `package.json` Update the name and description
17
- 5. Rename the `your-webcomponent.ts` and its associated `.test` file
18
- 6. Update `.travis.yml` with the proper secure key. See the [Travis docs](https://blog.travis-ci.com/2014-03-13-slack-notifications/) for more information.
19
-
20
- ## Local Demo with `web-dev-server`
5
+ ## Installation
21
6
  ```bash
22
- yarn start
7
+ yarn install --save @internetarchive/ia-book-actions
23
8
  ```
24
- To run a local development server that serves the basic demo located in `demo/index.html`
25
9
 
26
- ## Testing with Web Test Runner
27
- To run the suite of Web Test Runner tests, run
28
- ```bash
29
- yarn run test
10
+ ## Usage of Book Actions
11
+ ```html
12
+ <!-- index.html -->
13
+ <style>
14
+ html {
15
+ font-size: 10px;
16
+ /* This is to match petabox's base font size */
17
+ }
18
+ body {
19
+ margin: 0;
20
+ text-align: center;
21
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
22
+ }
23
+ #demo {
24
+ height: 50px;
25
+ }
26
+ ia-book-actions {
27
+ --white: #fff;
28
+ --black: #000;
29
+ --primaryBGColor: var(--black);
30
+ --secondaryBGColor: #222;
31
+ --tertiaryBGColor: #333;
32
+ --primaryTextColor: var(--white);
33
+ --primaryCTAFill: #194880;
34
+ --primaryCTABorder: #c5d1df;
35
+ --secondaryCTAFill: #333;
36
+ --secondaryCTABorder: #999;
37
+ --secondaryCTAFillRGB: 51, 51, 51;
38
+ --primaryErrorCTAFill: #e51c26;
39
+ --primaryErrorCTABorder: #f8c6c8;
40
+ --primaryCTAFillRGB: 25, 72, 128;
41
+ --primaryErrorCTAFillRGB: 229, 28, 38;
42
+ --primaryDisableCTAFill: #767676;
43
+ }
44
+ </style>
45
+
46
+ <ia-book-actions></ia-book-actions>
47
+
48
+ <script type="module">
49
+ import '@internetarchive/ia-book-actions.js';
50
+ import { ModalConfig } from '@internetarchive/modal-manager';
51
+ import {
52
+ defaultLendingStatus,
53
+ canBrowseAndBorrow,
54
+ } from '../src/assets/data/lending-get-status-service.js';
55
+
56
+ let iaBookActions = document.querySelector('ia-book-actions');
57
+ iaBookActions.userid = '@neeraj-archive';
58
+ iaBookActions.identifier = 'naturalhistoryof00unse_4111';
59
+ iaBookActions.bookTitle = 'This is test title for any book';
60
+ iaBookActions.lendingStatus = {}; // lendinStatus in json
61
+ iaBookActions.bwbPurchaseUrl = 'https://bwb.com/produce/123';
62
+ iaBookActions.barType = ''; // title|action
63
+ </script>
30
64
  ```
31
65
 
32
- To run the tests in watch mode (for &lt;abbr title=&#34;test driven development&#34;&gt;TDD&lt;/abbr&gt;, for example), run
66
+ ![Book Action Bar](./assets/ia-book-action-screen.png "Book Action Bar Demo")
33
67
 
34
- ```bash
35
- yarn run test:watch
68
+ ## Usage of Book Title
69
+ ```html
70
+ <!-- along with above properties, set barType to 'title' -->
71
+ <script type="module">
72
+ iaBookActions.barType = 'title'; // title|action
73
+ </script>
36
74
  ```
75
+ ![Book Title Bar](./assets/ia-book-title-screen.png "Book Title Bar Demo")
37
76
 
38
- ## Linting with ESLint, Prettier, and Types
39
- To scan the project for linting errors, run
40
- ```bash
41
- yarn run lint
42
- ```
77
+ ### Config Options
43
78
 
44
- You can lint with ESLint and Prettier individually as well
45
- ```bash
46
- yarn run lint:eslint
79
+ All of the config options:
80
+
81
+ ```javascript
82
+ let iaBookActions = document.querySelector('ia-book-actions');
83
+ const modalConfig = new ModalConfig(); // to show info/error modal
84
+
85
+ // set defaultLendingStatus for unavailable (without borrowables) book
86
+ iaBookActions.userid = '@jack';
87
+ iaBookActions.identifier = 'jack-sparrow';
88
+ iaBookActions.bookTitle = 'Contemporary Black biography. Volume 39 : profiles from the interContemporary Black biography. Volume 39';
89
+ iaBookActions.lendingStatus = {active_borrows: 0, active_browses: 0}, // complete lendingStatus object
90
+ iaBookActions.bwbPurchaseUrl = ''; // BWB purchase URl if any
91
+ iaBookActions.barType = '';
92
+ iaBookActions.modalConfig = modalConfig;
47
93
  ```
94
+
95
+ # Development
96
+
97
+ ## Prerequisite
48
98
  ```bash
49
- yarn run lint:prettier
99
+ yarn install
50
100
  ```
51
101
 
52
- To automatically fix many linting errors, run
102
+ ## Start Development Server
53
103
  ```bash
54
- yarn run format
104
+ yarn start
55
105
  ```
56
106
 
57
- You can format using ESLint and Prettier individually as well
107
+ ## Testing
58
108
  ```bash
59
- yarn run format:eslint
109
+ yarn test
60
110
  ```
111
+
112
+ ## Linting
61
113
  ```bash
62
- yarn run format:prettier
114
+ yarn lint
63
115
  ```
64
-
65
- ## Tooling configs
66
-
67
- For most of the tools, the configuration is in the `package.json` to reduce the amount of files in your project.
68
-
69
- If you customize the configuration a lot, you can consider moving them to individual files.
Binary file
Binary file
package/demo/index.html CHANGED
@@ -76,13 +76,9 @@
76
76
  overflow: hidden;
77
77
  }
78
78
 
79
- modal-manager {
79
+ modal-manager[mode='closed'] {
80
80
  display: none;
81
81
  }
82
-
83
- modal-manager[mode='open'] {
84
- display: block;
85
- }
86
82
  </style>
87
83
  </head>
88
84
 
@@ -108,6 +104,11 @@
108
104
  const modalConfig = new ModalConfig();
109
105
  modalConfig.headerColor = '#d9534f';
110
106
 
107
+ let lendingBarPostInit = function lendingBarPostInit() {
108
+ setTimeout(function() {
109
+ console.log('executed');
110
+ }, 100);
111
+ }
111
112
  // set defaultLendingStatus for unavailable (without borrowables) book
112
113
  iaBookActions.userid = userid;
113
114
  iaBookActions.identifier = identifier;
@@ -116,6 +117,7 @@
116
117
  iaBookActions.bwbPurchaseUrl = '';
117
118
  iaBookActions.barType = '';
118
119
  iaBookActions.modalConfig = modalConfig;
120
+ iaBookActions.lendingBarPostInit = lendingBarPostInit;
119
121
 
120
122
  let lendingStatus1 = iaBookActions.lendingStatus;
121
123
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@internetarchive/ia-book-actions",
3
- "version": "0.0.7-0",
3
+ "version": "0.0.7-11",
4
4
  "description": "Bookreader actions for borrowable book",
5
5
  "author": "Internet Archive",
6
6
  "license": "AGPL-3.0-only",
@@ -22,6 +22,7 @@
22
22
  "@internetarchive/icon-bookmark": "^1.1.3",
23
23
  "@internetarchive/icon-close": "^1.1.3",
24
24
  "@internetarchive/icon-info": "^1.2.6",
25
+ "@internetarchive/local-cache": "^0.2.0",
25
26
  "@internetarchive/modal-manager": "^0.1.0",
26
27
  "@internetarchive/shared-resize-observer": "^0.0.1",
27
28
  "lit-element": "^2.4.0",
@@ -27,7 +27,6 @@ export class CollapsibleActionGroup extends ActionsHandler {
27
27
  hasAdminAccess: { type: Boolean },
28
28
  dropdownArrow: { type: String },
29
29
  disabled: { type: Boolean },
30
- borrowType: { type: String },
31
30
  };
32
31
  }
33
32
 
@@ -46,45 +45,12 @@ export class CollapsibleActionGroup extends ActionsHandler {
46
45
  this.title = '';
47
46
  this.loaderIcon = 'https://archive.org/upload/images/tree/loading.gif';
48
47
  this.disabled = false;
49
- this.borrowType = ''; // (browsed|borrowed)
50
- this.consecutiveLoanCounts = 1; // consecutive loan count
51
48
  }
52
49
 
53
50
  updated(changed) {
54
- if (changed.has('width')) {
55
- if (this.isBelowTabletContainer) {
56
- this.resetActions();
57
- }
58
- }
59
-
60
- // this is execute to fetch loan token
61
- if (changed.has('borrowType')) {
62
- this.emitEnableBookAccess();
63
- }
64
- }
65
-
66
- /* emit custom event to fetch loan token */
67
- emitEnableBookAccess() {
68
- // send consecutiveLoanCounts for browsed books only.
69
- // for borrowed books, we just send [Counts-1]
70
- if (this.borrowType === 'browsed') {
71
- this.consecutiveLoanCounts =
72
- localStorage.getItem('consecutive-loan-count') ?? 1;
51
+ if (changed.has('width') && this.isBelowTabletContainer) {
52
+ this.resetActions();
73
53
  }
74
-
75
- this.dispatchEvent(
76
- new CustomEvent('enableBookAccess', {
77
- detail: {
78
- event: {
79
- category: `${this.borrowType}BookAccess`, // browsedBookAccess | borrowedBookAccess
80
- action: `${
81
- this.borrowType === 'browsed' ? 'BrowseCounts-' : 'Counts-'
82
- }${this.consecutiveLoanCounts}`, // (BrowseCounts-1|Counts-X)
83
- },
84
- borrowType: this.borrowType,
85
- },
86
- })
87
- );
88
54
  }
89
55
 
90
56
  /**
@@ -219,11 +185,13 @@ export class CollapsibleActionGroup extends ActionsHandler {
219
185
  }
220
186
 
221
187
  /**
222
- * Click handler to emit custom event on action click
223
- * @param { string } eventName
224
- * @param { object } gaEvent
188
+ * Dispatches click events when patron clicks on action buttons
189
+ * @param { string } eventName - actions like 'browseBook', 'borrowBook' etc...
190
+ * @param { object } gaEvent - contains analytics event action and category
225
191
  * @param { string } gaEvent.category
226
192
  * @param { string } gaEvent.action
193
+ *
194
+ * @fires CollapsibleActionGroup#{eventName} - (will be browseBook, borrowBook etc...)
227
195
  */
228
196
  clickHandler(eventName, gaEvent) {
229
197
  this.dropdownState = 'close';
@@ -13,12 +13,15 @@ export default function ActionsHandlerService(options) {
13
13
  ...options,
14
14
  };
15
15
 
16
- let baseHost = '';
17
- if (window.location.pathname === '/demo/') {
18
- baseHost = `/demo/`;
19
- } else {
20
- baseHost = `/services/loans/loan`;
21
- }
16
+ let baseHost = '/services/loans/loan';
17
+ const location = window?.location;
18
+
19
+ // return intentional error on dev environment
20
+ const shouldReturnError =
21
+ location?.href?.indexOf('?error=true') !== -1 &&
22
+ location?.hostname !== 'archive.org';
23
+
24
+ if (location?.pathname === '/demo/') baseHost = `/demo/`;
22
25
 
23
26
  let formData = new FormData();
24
27
  formData.append('action', option.action);
@@ -29,18 +32,16 @@ export default function ActionsHandlerService(options) {
29
32
  body: formData,
30
33
  })
31
34
  .then(response => {
32
- let shouldReturnError =
33
- window.location.href.indexOf('?error=true') !== -1;
34
35
  const tokenError = 'loan token not found. please try again later.';
35
36
  const borrowError =
36
37
  'This book is not available to borrow at this time. Please try again later.';
37
38
 
38
- // return error reponse if query param has ?error=true param...
39
+ // return error reponse when not production and has ?error=true param...
39
40
  const erroneousActions = ['browse_book', 'borrow_book', 'create_token'];
40
- if (shouldReturnError && erroneousActions.includes(option.action)) {
41
+ if (shouldReturnError && erroneousActions.includes(option?.action)) {
41
42
  return {
42
43
  success: false,
43
- error: option.action === 'create_token' ? tokenError : borrowError,
44
+ error: option?.action === 'create_token' ? tokenError : borrowError,
44
45
  };
45
46
  }
46
47
 
@@ -58,7 +59,7 @@ export default function ActionsHandlerService(options) {
58
59
  })
59
60
  .then(data => {
60
61
  // `data` is the parsed version of the JSON returned from the above endpoint.
61
- if (!data.error) {
62
+ if (!data?.error) {
62
63
  option?.success(data);
63
64
  } else {
64
65
  option?.error(data);
@@ -1,4 +1,6 @@
1
1
  import { LitElement } from 'lit-element';
2
+ import { LocalCache } from '@internetarchive/local-cache';
3
+
2
4
  import { URLHelper } from '../../config/url-helper.js';
3
5
  import ActionsHandlerService from './actions-handler-service.js';
4
6
 
@@ -15,16 +17,10 @@ export default class ActionsHandler extends LitElement {
15
17
  super();
16
18
  this.identifier = identifier;
17
19
  this.ajaxTimeout = 6000;
18
- this.loanTokenPollingDelay =
19
- window.location.pathname === '/demo/' ? 2000 : 120000; // 120000 ms = 2 min
20
- this.loanTokenInterval = null;
20
+ this.localCache = new LocalCache();
21
21
  this.bindEvents();
22
22
  }
23
23
 
24
- disconnectedCallback() {
25
- this.loanTokenInterval = undefined;
26
- }
27
-
28
24
  sendEvent(eventCategory, eventAction) {
29
25
  // eslint-disable-next-line no-console
30
26
  console?.log('Book action: ', { eventCategory, eventAction });
@@ -99,128 +95,117 @@ export default class ActionsHandler extends LitElement {
99
95
  const { category, action } = detail.event;
100
96
  this.sendEvent(category, action);
101
97
  });
102
-
103
- this.addEventListener('enableBookAccess', ({ detail }) => {
104
- const borrowType = detail?.borrowType;
105
-
106
- // fetch loan token for browsed/borrowed book and set an interval
107
- if (borrowType) {
108
- console.log('token poll started...');
109
-
110
- this.loanTokenInterval = setInterval(() => {
111
- this.handleLoanTokenPoller();
112
- }, this.loanTokenPollingDelay);
113
-
114
- const { category, action } = detail?.event;
115
- this.sendEvent(category, action);
116
- } else {
117
- // if book is not browsed, just clear token polling interval
118
- clearInterval(this.loanTokenInterval);
119
- }
120
- });
121
98
  }
122
99
 
123
100
  handleBrowseIt() {
124
- const context = 'browse_book';
125
- this.ActionError(context);
101
+ const action = 'browse_book';
102
+ this.dispatchToggleActionGroup();
126
103
 
127
104
  ActionsHandlerService({
128
- action: context,
105
+ action,
129
106
  identifier: this.identifier,
130
107
  success: () => {
131
108
  this.handleReadItNow();
132
109
  },
133
110
  error: data => {
134
- this.ActionError(context, data);
111
+ this.dispatchActionError(action, data);
135
112
  },
136
113
  });
137
114
  }
138
115
 
139
116
  handleReturnIt() {
140
- const context = 'return_loan';
141
- this.ActionError(context);
117
+ const action = 'return_loan';
118
+ this.dispatchToggleActionGroup();
142
119
 
143
120
  ActionsHandlerService({
144
- action: context,
121
+ action,
145
122
  identifier: this.identifier,
146
123
  success: () => {
147
124
  this.deleteLoanCookies();
148
125
  URLHelper.goToUrl(`/details/${this.identifier}`, true);
149
126
  },
150
127
  error: data => {
151
- this.ActionError(context, data);
128
+ this.dispatchActionError(action, data);
152
129
  },
153
130
  });
154
131
  }
155
132
 
156
133
  handleBorrowIt() {
157
- const context = 'borrow_book';
158
- this.ActionError(context);
134
+ const action = 'borrow_book';
135
+ this.dispatchToggleActionGroup();
159
136
 
160
137
  ActionsHandlerService({
161
- action: context,
138
+ action,
162
139
  identifier: this.identifier,
163
140
  success: () => {
164
141
  this.handleReadItNow();
165
142
  },
166
143
  error: data => {
167
- this.ActionError(context, data);
144
+ this.dispatchActionError(action, data);
168
145
  },
169
146
  });
170
147
  }
171
148
 
172
149
  handleReserveIt() {
173
- const context = 'join_waitlist';
174
- this.ActionError(context);
150
+ const action = 'join_waitlist';
151
+ this.dispatchToggleActionGroup();
175
152
 
176
153
  ActionsHandlerService({
177
- action: context,
154
+ action,
178
155
  identifier: this.identifier,
179
156
  success: () => {
180
157
  URLHelper.goToUrl(URLHelper.getRedirectUrl(), true);
181
158
  },
182
159
  error: data => {
183
- this.ActionError(context, data);
160
+ this.dispatchActionError(action, data);
184
161
  },
185
162
  });
186
163
  }
187
164
 
188
165
  handleRemoveFromWaitingList() {
189
- const context = 'leave_waitlist';
190
- this.ActionError(context);
166
+ const action = 'leave_waitlist';
167
+ this.dispatchToggleActionGroup();
191
168
 
192
169
  ActionsHandlerService({
193
- action: context,
170
+ action,
194
171
  identifier: this.identifier,
195
172
  success: () => {
196
173
  URLHelper.goToUrl(URLHelper.getRedirectUrl(), true);
197
174
  },
198
175
  error: data => {
199
- this.ActionError(context, data);
176
+ this.dispatchActionError(action, data);
200
177
  },
201
178
  });
202
179
  }
203
180
 
204
- handleLoanTokenPoller() {
205
- const context = 'create_token';
206
- ActionsHandlerService({
207
- identifier: this.identifier,
208
- action: context,
209
- error: data => {
210
- this.ActionError(context, data);
211
- clearInterval(this.loanTokenInterval); // stop token fetch api
212
- },
213
- });
214
- }
215
181
 
216
- ActionError(context, data = {}) {
182
+ /**
183
+ * Dispatches event when an error occured on action
184
+ * Notes:- toggle <collapsible-action-group> visibility (enable/disable).
185
+ *
186
+ * @param { String } action - name of action like browse_book, borrow_book
187
+ * @param { Object } data - erroneous response from api call
188
+ *
189
+ * @fires ActionsHandler#lendingActionError
190
+ */
191
+ dispatchActionError(action, data = {}) {
217
192
  this.dispatchEvent(
218
193
  new CustomEvent('lendingActionError', {
219
- detail: { context, data },
194
+ detail: { action, data },
220
195
  })
221
196
  );
222
197
  }
223
198
 
199
+ /**
200
+ * Dispatches event when patron is clicked on action buttons.
201
+ * Notes:- toggle <collapsible-action-group> disable/enable.
202
+ *
203
+ * @fires ActionsHandler#toggleActionGroup
204
+ */
205
+ dispatchToggleActionGroup() {
206
+ this.dispatchEvent(new CustomEvent('toggleActionGroup'));
207
+ }
208
+
224
209
  handleLoginOk() {
225
210
  /* eslint class-methods-use-this: "off" */
226
211
  const target = `/account/login?referer=${encodeURIComponent(
@@ -254,19 +239,25 @@ export default class ActionsHandler extends LitElement {
254
239
  }
255
240
 
256
241
  // save consecutive loan count for borrow
257
- setConsecutiveLoanCounts(action = '') {
258
- const { localStorage } = window;
259
- if (localStorage) {
242
+ async setConsecutiveLoanCounts(action = '') {
243
+ try {
260
244
  let newCount = 1;
261
245
  const storageKey = `consecutive-loan-count`;
262
- const existingCount = Number(localStorage.getItem(storageKey));
246
+ const existingCount = await this.localCache.get(storageKey);
263
247
 
264
- // want to increase browse-count by 1 if,
265
- // you consecutive reading a book.
266
- if (action === 'browseAgain') {
248
+ // increase browse-count by 1 when you consecutive reading a book.
249
+ if (action === 'browseAgain' && existingCount !== undefined) {
267
250
  newCount = existingCount ? existingCount + 1 : 1;
268
251
  }
269
- localStorage.setItem(storageKey, newCount);
252
+
253
+ // set new value
254
+ await this.localCache.set({
255
+ key: storageKey,
256
+ value: newCount,
257
+ ttl: 7200, // 2 hours
258
+ });
259
+ } catch (error) {
260
+ console.log(error);
270
261
  }
271
262
  }
272
263
 
@@ -75,9 +75,9 @@ export default class GetLendingActions {
75
75
  const disableBorrow = lendingStatus.loanCount >= lendingStatus.maxLoans;
76
76
 
77
77
  let primaryTitleText = '';
78
- let isBrowsed =
78
+ let isBrowsing =
79
79
  lendingStatus.user_has_browsed && !lendingStatus.browsingExpired;
80
- if (isBrowsed) {
80
+ if (isBrowsing) {
81
81
  primaryTitleText = this.getBrowseCountdownTitle();
82
82
  } else {
83
83
  primaryTitleText = `Your loan of this book has ${lendingStatus.daysLeftOnLoan} days left.`;
@@ -96,7 +96,7 @@ export default class GetLendingActions {
96
96
  this.actionsConfig.adminAccessConfig(),
97
97
  this.actionsConfig.purchaseConfig(),
98
98
  ],
99
- borrowType: isBrowsed ? 'browsed' : 'borrowed',
99
+ borrowType: isBrowsing ? 'browsed' : 'borrowed',
100
100
  };
101
101
  }
102
102
 
@@ -0,0 +1,110 @@
1
+ import { LitElement } from 'lit-element';
2
+ import { LocalCache } from '@internetarchive/local-cache';
3
+
4
+ import ActionsHandlerService from './actions-handler/actions-handler-service.js';
5
+
6
+ /**
7
+ * This class is used to create loan token for borrowed books
8
+ *
9
+ * ActionsHandlerService is a function being used to execute
10
+ */
11
+ export class LoanTokenPoller extends LitElement {
12
+ constructor() {
13
+ super();
14
+ this.identifier = '';
15
+ this.callback = () => {};
16
+ this.loanTokenPollingDelay = window.location.pathname === '/demo/' ? 2000 : 120000; // 120000 ms = 2 min
17
+ this.loanTokenInterval = undefined;
18
+ this.localCache = new LocalCache();
19
+ }
20
+
21
+ disconnectedCallback() {
22
+ this.loanTokenInterval = undefined;
23
+ }
24
+
25
+ async enableBookAccess(identifier, borrowType, callback) {
26
+ this.identifier = identifier
27
+ this.callback = callback;
28
+ let consecutiveLoanCounts = 1;
29
+
30
+ if (borrowType) {
31
+ // send consecutiveLoanCounts for browsed books only.
32
+ if (borrowType === 'browsed') {
33
+ try {
34
+ const existingCount = await this.localCache.get(
35
+ 'consecutive-loan-count'
36
+ );
37
+ consecutiveLoanCounts = existingCount ?? 1;
38
+ } catch (error) {
39
+ console.log(error);
40
+ }
41
+ }
42
+
43
+ // Do an initial token, then set an interval
44
+ this.handleLoanTokenPoller();
45
+
46
+ this.loanTokenInterval = setInterval(() => {
47
+ this.handleLoanTokenPoller();
48
+ }, this.loanTokenPollingDelay);
49
+
50
+ // event category and action for browsing book access
51
+ const category = `${borrowType}BookAccess`;
52
+ const action = `${
53
+ borrowType === 'browsed' ? 'BrowseCounts-' : 'Counts-'
54
+ }${consecutiveLoanCounts}`;
55
+ console.log(consecutiveLoanCounts)
56
+ this.sendEvent(category, action);
57
+ } else {
58
+ // if book is not browsed, just clear token polling interval
59
+ clearInterval(this.loanTokenInterval);
60
+ }
61
+ }
62
+
63
+ sendEvent(eventCategory, eventAction) {
64
+ // eslint-disable-next-line no-console
65
+ console?.log('Book action: ', { eventCategory, eventAction });
66
+ window?.archive_analytics?.send_event_no_sampling(
67
+ eventCategory,
68
+ eventAction,
69
+ `identifier=${this.identifier}`
70
+ );
71
+ }
72
+
73
+ async handleLoanTokenPoller() {
74
+ const action = 'create_token';
75
+ // await new Promise(r => setTimeout(r, 3000));
76
+ console.log(this.callback)
77
+ ActionsHandlerService({
78
+ identifier: this.identifier,
79
+ action,
80
+ error: data => {
81
+ console.log('error', data);
82
+ this.handleLendingActionError({detail: { action, data }})
83
+ // this.dispatchActionError(action, data);
84
+ clearInterval(this.loanTokenInterval); // stop token fetch api
85
+ this.callback();
86
+ },
87
+ success: () => {
88
+ this.callback();
89
+ }
90
+ });
91
+ }
92
+
93
+ /**
94
+ * Dispatches event when an error occured on action
95
+ * Notes:- toggle <collapsible-action-group> visibility (enable/disable).
96
+ *
97
+ * @param { String } action - name of action like browse_book, borrow_book
98
+ * @param { Object } data - erroneous response from api call
99
+ *
100
+ * @fires ActionsHandler#lendingActionError
101
+ */
102
+ dispatchActionError(action, data = {}) {
103
+ console.log('disptaced');
104
+ this.dispatchEvent(
105
+ new CustomEvent('lendingActionError', {
106
+ detail: { action, data },
107
+ })
108
+ );
109
+ }
110
+ }
@@ -1,5 +1,5 @@
1
1
  /* eslint-disable camelcase */
2
- import { html, css, LitElement } from 'lit-element';
2
+ import { html, css } from 'lit-element';
3
3
 
4
4
  import { SharedResizeObserver } from '@internetarchive/shared-resize-observer';
5
5
  import { ModalConfig } from '@internetarchive/modal-manager';
@@ -10,11 +10,10 @@ import './components/text-group.js';
10
10
  import './components/info-icon.js';
11
11
 
12
12
  import GetLendingActions from './core/services/get-lending-actions.js';
13
- import getRealTimeLendingStatus from './core/services/get-realtime-lending-status.js';
14
-
15
13
  import { mobileContainerWidth } from './core/config/constants.js';
14
+ import { LoanTokenPoller } from './core/services/loan-token-poller.js';
16
15
 
17
- export default class IABookActions extends LitElement {
16
+ export default class IABookActions extends LoanTokenPoller {
18
17
  static get properties() {
19
18
  return {
20
19
  userid: { type: String },
@@ -23,10 +22,11 @@ export default class IABookActions extends LitElement {
23
22
  lendingStatus: { type: Object },
24
23
  width: { type: Number },
25
24
  bwbPurchaseUrl: { type: String },
25
+ lendingBarPostInit: { type: Function },
26
26
  barType: { type: String },
27
27
  sharedObserver: { attribute: false },
28
28
  disableActionGroup: { type: Boolean },
29
- modalConfig: { type: Object },
29
+ modal: { Object },
30
30
  };
31
31
  }
32
32
 
@@ -38,16 +38,20 @@ export default class IABookActions extends LitElement {
38
38
  this.lendingStatus = {};
39
39
  this.width = 0;
40
40
  this.bwbPurchaseUrl = '';
41
+ this.lendingBarPostInit = () => {};
41
42
  this.barType = 'action'; // 'title'|'action'
42
43
  this.sharedObserver = undefined;
44
+ this.disableActionGroup = false;
45
+ this.modal = undefined;
46
+
47
+ // private props
43
48
  this.primaryActions = [];
44
49
  this.primaryTitle = '';
45
50
  this.primaryColor = 'primary';
46
51
  this.secondaryActions = [];
47
52
  this.lendingOptions = {};
48
- this.disableActionGroup = false;
49
- this.modalConfig = {};
50
- this.realtimeInternal = 0; // store intervalID
53
+ this.borrowType = ''; // (browsed|borrowed)
54
+ this.consecutiveLoanCounts = 1; // consecutive loan count
51
55
  }
52
56
 
53
57
  disconnectedCallback() {
@@ -59,29 +63,10 @@ export default class IABookActions extends LitElement {
59
63
  this.sharedObserver = new SharedResizeObserver();
60
64
  this.setupResizeObserver();
61
65
  }
62
-
63
- if (!Object.keys(this.modalConfig).length) {
64
- this.modalConfig = new ModalConfig();
65
- this.modalConfig.headerColor = '#d9534f';
66
- }
67
- this.setupLendingToolbarActions();
68
-
69
- // fetch latest lending status after an internal
70
- if (
71
- this.lendingStatus.user_has_browsed ||
72
- this.lendingStatus.user_has_borrowed
73
- ) {
74
- this.realtimeInternal = setInterval(() => {
75
- const latestLendingStatus = getRealTimeLendingStatus(
76
- 'naturalhistoryof00unse_4'
77
- );
78
- console.log(latestLendingStatus);
79
- if (latestLendingStatus) this.lendingStatus = latestLendingStatus;
80
- }, 30000); // 60000 ms = 1 min
81
- }
82
66
  }
83
67
 
84
68
  updated(changed) {
69
+ // console.log(changed)
85
70
  if (changed.has('lendingStatus') || changed.has('bwbPurchaseUrl')) {
86
71
  this.setupLendingToolbarActions();
87
72
  this.update();
@@ -172,14 +157,28 @@ export default class IABookActions extends LitElement {
172
157
  // when browse is completed, we shows browse-again button
173
158
  this.startBrowseTimer();
174
159
  }
160
+
161
+ console.log('this.borrowType', this.borrowType);
162
+
163
+ /**
164
+ * this.lendingBarPostInit() callback function use to:-
165
+ * - disptach lendingFlow::PostInit event
166
+ * - initialize bookreader using br.init()
167
+ */
168
+ if (this.borrowType) {
169
+ // enable access of borrowed/browsed books
170
+ this.enableBookAccess(this.identifier, this.borrowType, this.lendingBarPostInit);
171
+ } else {
172
+ this.lendingBarPostInit();
173
+ }
175
174
  }
176
175
 
177
176
  render() {
178
- return html`
179
- <section class="lending-wrapper">
180
- ${this.barType === 'title' ? this.bookTitleBar : this.bookActionBar}
181
- </section>
182
- `;
177
+ if (this.barType === 'title') {
178
+ return html`<section class="lending-wrapper">${this.bookTitleBar}</section>`;
179
+ }
180
+
181
+ return html`<section class="lending-wrapper">${this.bookActionBar}</section>`;
183
182
  }
184
183
 
185
184
  get bookTitleBar() {
@@ -202,6 +201,7 @@ export default class IABookActions extends LitElement {
202
201
  ?hasAdminAccess=${this.hasAdminAccess}
203
202
  ?disabled=${this.disableActionGroup}
204
203
  @lendingActionError=${this.handleLendingActionError}
204
+ @toggleActionGroup=${this.handleToggleActionGroup}
205
205
  >
206
206
  </collapsible-action-group>
207
207
  ${this.textGroupTemplate} ${this.infoIconTemplate}
@@ -209,27 +209,41 @@ export default class IABookActions extends LitElement {
209
209
  }
210
210
 
211
211
  /*
212
- * handle lending errors occure on different operation like
212
+ * custom event handler to toggle action group visibility
213
+ *
214
+ * @event IABookActions#toggleActionGroup
215
+ */
216
+ handleToggleActionGroup() {
217
+ this.disableActionGroup = !this.disableActionGroup;
218
+ }
219
+
220
+ /*
221
+ * handle lending errors occure during different operation like
213
222
  * browse book, borrow book, borrowed book token etc...
223
+ *
224
+ * @event IABookActions#lendingActionError
225
+ * @param {Object} event - The employee who is responsible for the project
226
+ * @param {string} event.detail.action - action when error occurred like 'browseBook', 'borrowBook'
227
+ * @param {string} event.detail.data.error - error message
214
228
  */
215
- handleLendingActionError(e) {
229
+ handleLendingActionError(event) {
216
230
  // toggle activity loader
217
- this.disableActionGroup = !this.disableActionGroup;
231
+ this.handleToggleActionGroup();
218
232
 
219
- const context = e?.detail?.context;
220
- const errorMsg = e?.detail?.data?.error;
233
+ const action = event?.detail?.action;
234
+ const errorMsg = event?.detail?.data?.error;
221
235
 
222
- if (errorMsg) this.showErrorModal(context, errorMsg);
236
+ if (errorMsg) this.showErrorModal(errorMsg, action);
223
237
 
224
238
  // update action bar state if book is not available to browse or borrow.
225
239
  if (errorMsg && errorMsg.match(/not available to borrow/gm)) {
226
240
  let currStatus = this.lendingStatus;
227
- if (context === 'browse_book') {
241
+ if (action === 'browse_book') {
228
242
  currStatus = {
229
243
  ...this.lendingStatus,
230
244
  available_to_browse: false,
231
245
  };
232
- } else if (context === 'borrow_book') {
246
+ } else if (action === 'borrow_book') {
233
247
  currStatus = {
234
248
  ...this.lendingStatus,
235
249
  available_to_borrow: false,
@@ -240,24 +254,42 @@ export default class IABookActions extends LitElement {
240
254
  }
241
255
 
242
256
  /* show error message if something went wrong */
243
- async showErrorModal(context, errorMsg) {
244
- // add modal-manager in DOM to show alert message
245
- this.modal = document.createElement('modal-manager');
246
- this.modal.id = 'action-bar-modal';
247
- await document.body.appendChild(this.modal);
257
+ async showErrorModal(errorMsg, action) {
258
+ this.disableActionGroup = false;
248
259
 
249
- // fallback if <modal-manager> is not found!
260
+ // check if this.modal passed as prop
250
261
  if (!this.modal) {
251
- alert(errorMsg);
252
- return;
262
+ this.modal = document.querySelector('modal-manager');
263
+
264
+ // check the DOM if <modal-manager> already there
265
+ if (!this.modal) this.modal = document.createElement('modal-manager');
253
266
  }
254
267
 
255
- this.disableActionGroup = false;
256
- this.modalConfig.title = 'Lending error';
257
- this.modalConfig.message = errorMsg;
268
+ this.modal.id = 'action-bar-modal';
269
+ await document.body.appendChild(this.modal);
270
+
271
+ const modalConfig = new ModalConfig({
272
+ title: 'Lending error',
273
+ message: errorMsg,
274
+ headerColor: '#d9534f',
275
+ });
276
+
277
+ if (action === 'create_token') {
278
+ modalConfig.closeOnBackdropClick = false;
279
+ modalConfig.showCloseButton = false;
280
+ modalConfig.message = html` Uh oh, something went wrong trying to access
281
+ this book.<br />
282
+ Please <a href="${() => window.location.reload(true)}">refresh</a> to
283
+ try again or send us an email to
284
+ <a
285
+ href="mailto:info@archive.org?subject=Help: cannot access my borrowed book: ${this
286
+ .identifier}"
287
+ >info@archive.org</a
288
+ >`;
289
+ }
258
290
 
259
291
  this.modal.showModal({
260
- config: this.modalConfig,
292
+ config: modalConfig,
261
293
  });
262
294
  }
263
295
 
@@ -1,6 +1,5 @@
1
1
  import { html, fixture, expect } from '@open-wc/testing';
2
2
  import '../../src/components/collapsible-action-group.js';
3
- import Sinon from 'sinon';
4
3
 
5
4
  const container = ({
6
5
  primaryActions = [
@@ -85,27 +84,3 @@ describe('<collapsible-action-group>', () => {
85
84
  expect(eAnalytics.action).to.equal('Browse');
86
85
  });
87
86
  });
88
-
89
- describe('fetch loan token and localStorage for browsed/borrowsed book', () => {
90
- beforeEach(() => {
91
- const localStorageMock = {
92
- getItem: Sinon.spy(),
93
- setItem: Sinon.spy(),
94
- };
95
- Object.defineProperty(window, 'localStorage', {
96
- value: localStorageMock,
97
- });
98
- });
99
-
100
- it('test localStorage function when borrowType is browsed', async () => {
101
- await fixture(container({ borrowType: 'browsed' }));
102
- expect(localStorage.getItem).to.be.calledOnce;
103
- expect(localStorage.getItem).to.be.calledWith('consecutive-loan-count');
104
- });
105
-
106
- it('test localStorage function when borrowType is borrowed', async () => {
107
- await fixture(container({ borrowType: 'borrowed' }));
108
- expect(localStorage.getItem).to.not.be.calledOnce;
109
- expect(localStorage.getItem).to.not.be.calledWith('consecutive-loan-count');
110
- });
111
- });
@@ -122,7 +122,7 @@ describe('Borrow status actions', () => {
122
122
  el.addEventListener('lendingActionError', () => {
123
123
  el.handleLendingActionError({
124
124
  detail: {
125
- context: 'browse_book',
125
+ action: 'browse_book',
126
126
  data: { error: 'not available to borrow' },
127
127
  },
128
128
  });
@@ -151,7 +151,7 @@ describe('Borrow status actions', () => {
151
151
  el.addEventListener('lendingActionError', () => {
152
152
  el.handleLendingActionError({
153
153
  detail: {
154
- context: 'borrow_book',
154
+ action: 'borrow_book',
155
155
  data: { error: 'not available to borrow' },
156
156
  },
157
157
  });
@@ -1,27 +0,0 @@
1
- /*
2
- * Helper function to fetch real-time lending status
3
- */
4
- export default function getRealTimeLendingStatus(identifier) {
5
- let url = `https://ia-petabox-neeraj.archive.org/services/book/lending-info.php?identifier=${identifier}`;
6
-
7
- if (window.location.pathname === '/demo/') {
8
- url = ''; // change URL to prevent error on localhost
9
- }
10
-
11
- fetch(url)
12
- .then(response => {
13
- // return { response_code: '400', data: 'error happened' };
14
- return response.json();
15
- })
16
- .then(data => {
17
- let newLendingInfo = data;
18
- const newLendingStatus = data?.lendingStatus;
19
- newLendingInfo = { ...newLendingInfo, ...newLendingStatus };
20
- newLendingInfo.lendingStatus = '';
21
-
22
- return newLendingInfo;
23
- })
24
- .catch(data => {
25
- return data;
26
- });
27
- }