@internetarchive/ia-book-actions 0.0.6-6 → 0.0.6-7

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
@@ -71,6 +71,16 @@
71
71
  float: left;
72
72
  margin: 2px;
73
73
  }
74
+
75
+ #ui-overlay {
76
+ position: fixed;
77
+ top: 0;
78
+ left: 0;
79
+ width: 100%;
80
+ height: 100%;
81
+ background: black;
82
+ opacity: 0.3;
83
+ }
74
84
  </style>
75
85
  </head>
76
86
 
@@ -146,7 +156,7 @@
146
156
  if (e.target.value === 'user_has_browsed') {
147
157
  lendingStatus1.user_has_browsed = true;
148
158
  lendingStatus1.available_to_browse = false;
149
- lendingStatus1.secondsLeftOnLoan = 10;
159
+ lendingStatus1.secondsLeftOnLoan = 30;
150
160
  lendingStatus1.browsingExpired = false;
151
161
  } else if(e.target.value === 'browsingExpired') {
152
162
  lendingStatus1.user_has_browsed = true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@internetarchive/ia-book-actions",
3
- "version": "0.0.6-6",
3
+ "version": "0.0.6-7",
4
4
  "description": "Bookreader actions for borrowable book",
5
5
  "author": "Internet Archive",
6
6
  "license": "AGPL-3.0-only",
@@ -21,6 +21,7 @@
21
21
  "@internetarchive/ia-icons": "^1.2.2",
22
22
  "@internetarchive/icon-bookmark": "^1.1.3",
23
23
  "@internetarchive/icon-info": "^1.2.6",
24
+ "@internetarchive/icon-close": "^1.1.3",
24
25
  "@internetarchive/shared-resize-observer": "^0.0.1",
25
26
  "lit-element": "^2.4.0",
26
27
  "lit-html": "^1.3.0"
@@ -27,6 +27,7 @@ export class CollapsibleActionGroup extends ActionsHandler {
27
27
  hasAdminAccess: { type: Boolean },
28
28
  dropdownArrow: { type: String },
29
29
  disabled: { type: Boolean },
30
+ bookHasBrowsed: { type: Boolean },
30
31
  };
31
32
  }
32
33
 
@@ -45,6 +46,13 @@ export class CollapsibleActionGroup extends ActionsHandler {
45
46
  this.title = '';
46
47
  this.loaderIcon = 'https://archive.org/upload/images/tree/loading.gif';
47
48
  this.disabled = false;
49
+ this.bookHasBrowsed = false;
50
+ }
51
+
52
+ firstUpdated() {
53
+ if (this.bookHasBrowsed) {
54
+ this.emitFetchLoanToken();
55
+ }
48
56
  }
49
57
 
50
58
  updated(changed) {
@@ -53,6 +61,23 @@ export class CollapsibleActionGroup extends ActionsHandler {
53
61
  this.resetActions();
54
62
  }
55
63
  }
64
+
65
+ // this is execute on demo page only
66
+ if (changed.has('bookHasBrowsed')) {
67
+ this.emitFetchLoanToken();
68
+ }
69
+ }
70
+
71
+ /* emit custom event to fetch loan token */
72
+ emitFetchLoanToken() {
73
+ this.dispatchEvent(
74
+ new CustomEvent('fetchLoanToken', {
75
+ detail: {
76
+ event: { category: 'action' },
77
+ bookHasBrowsed: this.bookHasBrowsed,
78
+ },
79
+ })
80
+ );
56
81
  }
57
82
 
58
83
  /**
@@ -0,0 +1,139 @@
1
+ /* eslint-disable */
2
+ import { LitElement, html, css } from 'lit-element';
3
+ import { classMap } from 'lit-html/directives/class-map';
4
+
5
+ import '@internetarchive/icon-close/icon-close.js';
6
+ import buttonBaseStyle from '../assets/styles/ia-button.js';
7
+
8
+ export class ShowDialog extends LitElement {
9
+ static get properties() {
10
+ return {
11
+ title: { type: String },
12
+ body: { type: String },
13
+ actions: { type: Array },
14
+ opened: { type: Boolean },
15
+ };
16
+ }
17
+
18
+ constructor() {
19
+ super();
20
+ this.title = 'Error';
21
+ this.body =
22
+ 'Unexpected error: loan does not exist. Please try deleting your archive.org cookie.';
23
+ this.actions = [];
24
+ this.opened = false;
25
+ }
26
+
27
+ updated(changed) {
28
+ if (changed.get('opened') === false) {
29
+ this.addOverlay();
30
+ } else {
31
+ this.removeOverlay();
32
+ }
33
+ }
34
+
35
+ render() {
36
+ return html`
37
+ <div
38
+ class="${classMap({
39
+ dialog: true,
40
+ opened: this.opened,
41
+ closed: !this.opened,
42
+ })}"
43
+ >
44
+ <div class="dialog-head">
45
+ ${this.title}
46
+ <ia-icon-close
47
+ @click="${() =>
48
+ this.dispatchEvent(new CustomEvent('dialogAlertClose'))}"
49
+ ></ia-icon-close>
50
+ </div>
51
+ <div class="dialog-body">${this.body}</div>
52
+ <div class="dialog-foot">${this.renderActionButton}</div>
53
+ </div>
54
+ `;
55
+ }
56
+
57
+ /* exceptional case to use dom element */
58
+ addOverlay() {
59
+ if (this.opened && !document.getElementById('ui-overlay')) {
60
+ const overlayElement = document.createElement('div');
61
+ overlayElement.id = 'ui-overlay';
62
+ document.getElementsByTagName('body')[0].appendChild(overlayElement);
63
+ }
64
+ }
65
+
66
+ /* exceptional case to use dom element */
67
+ removeOverlay() {
68
+ const overlayElement = document.getElementById('ui-overlay');
69
+ if (overlayElement) overlayElement.remove();
70
+ }
71
+
72
+ get renderActionButton() {
73
+ if (!this.actions) return;
74
+
75
+ return this.actions.map(
76
+ action =>
77
+ html`<a class=${action.className} href=${action.href}>
78
+ ${action.text}
79
+ </button>`
80
+ );
81
+ }
82
+
83
+ static get styles() {
84
+ const dialogStyle = css`
85
+ :host {
86
+ color: #343434;
87
+ font-size: 1.6rem;
88
+ }
89
+ .dialog {
90
+ position: absolute;
91
+ background: #fff;
92
+ border-radius: 1.2rem;
93
+ z-index: 3000;
94
+ flex-direction: column;
95
+ min-width: 300px;
96
+ max-width: 400px;
97
+ left: 50%;
98
+ transform: translateX(-50%);
99
+
100
+ border: 2px solid #ccc;
101
+ border-radius: 12px;
102
+ box-shadow: 0px 0px 27px 6px rgb(66 66 66 / 80%);
103
+ }
104
+ .dialog-head,
105
+ .dialog-body,
106
+ .dialog-foot {
107
+ padding: 1rem;
108
+ user-select: none;
109
+ text-align: left;
110
+ }
111
+ .dialog-body {
112
+ border: 1px solid #d8d8d8;
113
+ }
114
+ ia-icon-close {
115
+ position: absolute;
116
+ right: 0.5rem;
117
+ top: 0.5rem;
118
+ width: 2.5rem;
119
+ height: 2.5rem;
120
+ vertical-align: middle;
121
+ --iconFillColor: #333;
122
+ cursor: pointer;
123
+ }
124
+ .opened {
125
+ display: flex;
126
+ }
127
+ .closed {
128
+ display: none;
129
+ }
130
+ a {
131
+ text-decoration: none;
132
+ }
133
+ `;
134
+
135
+ return [buttonBaseStyle, dialogStyle];
136
+ }
137
+ }
138
+
139
+ customElements.define('show-dialog', ShowDialog);
@@ -34,6 +34,12 @@ export default function ActionsHandlerService(options) {
34
34
  .then(response => {
35
35
  // test changes, (won't affect you)
36
36
  if (baseHost == '/demo/1' || baseHost == '/demo/') {
37
+ if (option.action == 'create_token') {
38
+ return {
39
+ error: 'loan token not found. please try again later.',
40
+ };
41
+ }
42
+
37
43
  return {
38
44
  error:
39
45
  'This book is not available to borrow at this time. Please try again later.',
@@ -46,7 +52,6 @@ export default function ActionsHandlerService(options) {
46
52
  })
47
53
  .then(data => {
48
54
  // `data` is the parsed version of the JSON returned from the above endpoint.
49
- console.log('actual reponse for endpoint', data);
50
55
  if (!data.error) {
51
56
  option?.success(data);
52
57
  } else {
@@ -15,6 +15,8 @@ export default class ActionsHandler extends LitElement {
15
15
  super();
16
16
  this.identifier = identifier;
17
17
  this.ajaxTimeout = 6000;
18
+ this.loanTokenPollingDelay = 20000; // 20000 ms = 20 sec
19
+ this.loanTokenInterval = null;
18
20
  this.bindEvents();
19
21
  }
20
22
 
@@ -84,6 +86,22 @@ export default class ActionsHandler extends LitElement {
84
86
  const { category, action } = detail.event;
85
87
  this.sendEvent(category, action);
86
88
  });
89
+
90
+ this.addEventListener('fetchLoanToken', ({ detail }) => {
91
+ const bookHasBrowsed = detail?.bookHasBrowsed;
92
+
93
+ // fetch loan token and set an interval
94
+ if (bookHasBrowsed) {
95
+ console.log('token poll started...');
96
+
97
+ this.loanTokenInterval = setInterval(() => {
98
+ this.handleLoanTokenPoller();
99
+ }, this.loanTokenPollingDelay);
100
+ } else {
101
+ // if book is not browsed, just clear token polling interval
102
+ clearInterval(this.loanTokenInterval);
103
+ }
104
+ });
87
105
  }
88
106
 
89
107
  handleBrowseIt() {
@@ -97,7 +115,6 @@ export default class ActionsHandler extends LitElement {
97
115
  this.handleReadItNow();
98
116
  },
99
117
  error: data => {
100
- alert(data.error);
101
118
  this.ActionError(context, data);
102
119
  },
103
120
  });
@@ -115,8 +132,7 @@ export default class ActionsHandler extends LitElement {
115
132
  URLHelper.goToUrl(`/details/${this.identifier}`, true);
116
133
  },
117
134
  error: data => {
118
- alert(data.error);
119
- this.ActionError(context);
135
+ this.ActionError(context, data);
120
136
  },
121
137
  });
122
138
  }
@@ -132,7 +148,6 @@ export default class ActionsHandler extends LitElement {
132
148
  this.handleReadItNow();
133
149
  },
134
150
  error: data => {
135
- alert(data.error);
136
151
  this.ActionError(context, data);
137
152
  },
138
153
  });
@@ -149,8 +164,7 @@ export default class ActionsHandler extends LitElement {
149
164
  URLHelper.goToUrl(URLHelper.getRedirectUrl(), true);
150
165
  },
151
166
  error: data => {
152
- alert(data.error);
153
- this.ActionError(context);
167
+ this.ActionError(context, data);
154
168
  },
155
169
  });
156
170
  }
@@ -166,12 +180,36 @@ export default class ActionsHandler extends LitElement {
166
180
  URLHelper.goToUrl(URLHelper.getRedirectUrl(), true);
167
181
  },
168
182
  error: data => {
169
- alert(data.error);
170
- this.ActionError(context);
183
+ this.ActionError(context, data);
184
+ },
185
+ });
186
+ }
187
+
188
+ handleLoanTokenPoller() {
189
+ const context = 'create_token';
190
+ this.ActionError(context);
191
+
192
+ ActionsHandlerService({
193
+ identifier: this.identifier,
194
+ action: context,
195
+ loader: false,
196
+ success: () => {
197
+ this.handleReadItNow();
198
+ },
199
+ error: data => {
200
+ this.ActionError(context, data);
171
201
  },
172
202
  });
173
203
  }
174
204
 
205
+ ActionError(context, data = {}) {
206
+ this.dispatchEvent(
207
+ new CustomEvent('lendingActionError', {
208
+ detail: { context, data },
209
+ })
210
+ );
211
+ }
212
+
175
213
  handleLoginOk() {
176
214
  /* eslint class-methods-use-this: "off" */
177
215
  const target = `/account/login?referer=${encodeURIComponent(
@@ -218,12 +256,4 @@ export default class ActionsHandler extends LitElement {
218
256
  cookie += '; path=/; domain=.archive.org;';
219
257
  document.cookie = cookie;
220
258
  }
221
-
222
- ActionError(context, data = {}) {
223
- this.dispatchEvent(
224
- new CustomEvent('lendingActionError', {
225
- detail: { context, data },
226
- })
227
- );
228
- }
229
259
  }
@@ -7,6 +7,7 @@ import './components/collapsible-action-group.js';
7
7
  import './components/book-title-bar.js';
8
8
  import './components/text-group.js';
9
9
  import './components/info-icon.js';
10
+ import './components/dialog-alert.js';
10
11
 
11
12
  import GetLendingActions from './core/services/get-lending-actions.js';
12
13
  import { mobileContainerWidth } from './core/config/constants.js';
@@ -23,6 +24,8 @@ export default class IABookActions extends LitElement {
23
24
  barType: { type: String },
24
25
  sharedObserver: { attribute: false },
25
26
  disable: { type: Boolean },
27
+ dialogVisible: { type: Boolean },
28
+ bookHasBrowsed: { type: Boolean },
26
29
  };
27
30
  }
28
31
 
@@ -41,7 +44,9 @@ export default class IABookActions extends LitElement {
41
44
  this.primaryColor = 'primary';
42
45
  this.secondaryActions = [];
43
46
  this.lendingOptions = {};
44
- this.disable = false;
47
+ this.disableActionGroup = false;
48
+ this.dialogVisible = false;
49
+ this.bookHasBrowsed = false;
45
50
  }
46
51
 
47
52
  disconnectedCallback() {
@@ -141,10 +146,12 @@ export default class IABookActions extends LitElement {
141
146
  return action != null;
142
147
  });
143
148
 
144
- if (
149
+ this.bookHasBrowsed =
145
150
  this.lendingStatus.user_has_browsed &&
146
- !this.lendingStatus.browseHasExpired
147
- ) {
151
+ !this.lendingStatus.browsingExpired;
152
+ if (this.bookHasBrowsed) {
153
+ // start timer for 1-hour borrowed book.
154
+ // when 1 hour is completed, we shows browse-again button
148
155
  this.startBrowseTimer();
149
156
  }
150
157
  }
@@ -154,9 +161,21 @@ export default class IABookActions extends LitElement {
154
161
  <section class="lending-wrapper">
155
162
  ${this.barType === 'title' ? this.bookTitleBar : this.bookActionBar}
156
163
  </section>
164
+
165
+ <show-dialog
166
+ .title=${this.dialogTitle}
167
+ .body=${this.dialogBody}
168
+ .actions=${this.dialogActions}
169
+ ?opened="${this.dialogVisible}"
170
+ @dialogAlertClose="${() => this.closeDialogAlert()}"
171
+ ></show-dialog>
157
172
  `;
158
173
  }
159
174
 
175
+ closeDialogAlert() {
176
+ this.dialogVisible = false;
177
+ }
178
+
160
179
  get bookTitleBar() {
161
180
  return html`<book-title-bar
162
181
  .identifier=${this.identifier}
@@ -174,7 +193,8 @@ export default class IABookActions extends LitElement {
174
193
  .secondaryActions=${this.secondaryActions}
175
194
  .width=${this.width}
176
195
  .hasAdminAccess=${this.hasAdminAccess}
177
- .disabled=${this.disable}
196
+ .disabled=${this.disableActionGroup}
197
+ .bookHasBrowsed=${this.bookHasBrowsed}
178
198
  @lendingActionError=${this.handleLendingActionError}
179
199
  >
180
200
  </collapsible-action-group>
@@ -184,11 +204,16 @@ export default class IABookActions extends LitElement {
184
204
 
185
205
  handleLendingActionError(e) {
186
206
  // toggle activity loader
187
- this.disable = !this.disable;
207
+ this.disableActionGroup = !this.disableActionGroup;
188
208
 
189
209
  const context = e?.detail?.context;
190
210
  const errorMsg = e?.detail?.data?.error;
191
211
 
212
+ // early exist if you don't have errorMsg
213
+ if (!errorMsg) return;
214
+
215
+ this.initializeDialogAlert(context, errorMsg);
216
+
192
217
  // update action bar state if book is not available to browse or borrow.
193
218
  if (errorMsg && errorMsg.match(/not available to borrow/gm)) {
194
219
  let currStatus = this.lendingStatus;
@@ -207,6 +232,24 @@ export default class IABookActions extends LitElement {
207
232
  }
208
233
  }
209
234
 
235
+ /* set props value to show dialog alert */
236
+ initializeDialogAlert(context, errorMsg) {
237
+ this.dialogVisible = true;
238
+ this.dialogTitle = 'Sorry!';
239
+ this.dialogBody = errorMsg;
240
+ this.dialogActions = [];
241
+
242
+ if (context === 'create_token') {
243
+ this.dialogActions = [
244
+ {
245
+ className: 'ia-button primary',
246
+ text: 'Back to Item Details',
247
+ href: `/details/${this.lendingStatus.bookUrl}`,
248
+ },
249
+ ];
250
+ }
251
+ }
252
+
210
253
  get iconClass() {
211
254
  return this.width <= mobileContainerWidth ? 'mobile' : 'desktop';
212
255
  }