@abi-software/map-side-bar 1.3.32 → 1.3.34

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.
@@ -1,508 +1,508 @@
1
- <template>
2
- <el-card :body-style="bodyStyle" class="content-card">
3
- <div slot="header" class="header">
4
- <context-card v-if="contextCardEntry && contextCardEnabled" :entry="contextCardEntry" :envVars="envVars"/>
5
- <el-input
6
- class="search-input"
7
- placeholder="Search"
8
- v-model="searchInput"
9
- @keyup.native="searchEvent"
10
- clearable
11
- @clear="clearSearchClicked"
12
- ></el-input>
13
- <el-button class="button" @click="searchEvent">Search</el-button>
14
- </div>
15
- <SearchFilters
16
- class="filters"
17
- ref="filtersRef"
18
- :entry="filterEntry"
19
- :envVars="envVars"
20
- @filterResults="filterUpdate"
21
- @numberPerPage="numberPerPageUpdate"
22
- @loading="filtersLoading"
23
- @cascaderReady="cascaderReady"
24
- ></SearchFilters>
25
- <div class="content scrollbar" v-loading="loadingCards" ref="content">
26
- <div
27
- class="error-feedback"
28
- v-if="results.length === 0 && !loadingCards"
29
- >No results found - Please change your search / filter criteria.</div>
30
- <div v-for="result in results" :key="result.doi" class="step-item">
31
- <DatasetCard :entry="result" :envVars="envVars" @contextUpdate="contextCardUpdate"></DatasetCard>
32
- </div>
33
- <el-pagination
34
- class="pagination"
35
- :current-page.sync="page"
36
- hide-on-single-page
37
- large
38
- layout="prev, pager, next"
39
- :page-size="numberPerPage"
40
- :total="numberOfHits"
41
- @current-change="pageChange"
42
- ></el-pagination>
43
- </div>
44
- </el-card>
45
- </template>
46
-
47
-
48
- <script>
49
- /* eslint-disable no-alert, no-console */
50
- import Vue from "vue";
51
- import {
52
- Button,
53
- Card,
54
- Drawer,
55
- Icon,
56
- Input,
57
- Loading,
58
- Pagination
59
- } from "element-ui";
60
- import lang from "element-ui/lib/locale/lang/en";
61
- import locale from "element-ui/lib/locale";
62
- import SearchFilters from "./SearchFilters";
63
- import DatasetCard from "./DatasetCard";
64
- import ContextCard from "./ContextCard.vue";
65
- import EventBus from "./EventBus"
66
-
67
- import {AlgoliaClient} from "../algolia/algolia.js";
68
- import {getFilters} from "../algolia/utils.js"
69
-
70
- locale.use(lang);
71
- Vue.use(Button);
72
- Vue.use(Card);
73
- Vue.use(Drawer);
74
- Vue.use(Icon);
75
- Vue.use(Input);
76
- Vue.use(Loading);
77
- Vue.use(Pagination);
78
-
79
- // handleErrors: A custom fetch error handler to recieve messages from the server
80
- // even when an error is found
81
- var handleErrors = async function(response) {
82
- if (!response.ok) {
83
- let parse = await response.json();
84
- if (parse) {
85
- throw new Error(parse.message);
86
- } else {
87
- throw new Error(response);
88
- }
89
- }
90
- return response;
91
- };
92
-
93
- var initial_state = {
94
- searchInput: "",
95
- lastSearch: "",
96
- results: [],
97
- numberOfHits: 0,
98
- filter: [],
99
- loadingCards: false,
100
- numberPerPage: 10,
101
- page: 1,
102
- pageModel: 1,
103
- start: 0,
104
- hasSearched: false,
105
- contextCardEntry: undefined,
106
- contextCardEnabled: true,
107
- };
108
-
109
- export default {
110
- components: { SearchFilters, DatasetCard, ContextCard },
111
- name: "SideBarContent",
112
- props: {
113
- visible: {
114
- type: Boolean,
115
- default: false
116
- },
117
- isDrawer: {
118
- type: Boolean,
119
- default: true
120
- },
121
- entry: {
122
- type: Object,
123
- default: () => initial_state
124
- },
125
- envVars: {
126
- type: Object,
127
- default: () => {}
128
- },
129
- },
130
- data: function() {
131
- return {
132
- ...this.entry,
133
- bodyStyle: {
134
- flex: "1 1 auto",
135
- "flex-flow": "column",
136
- display: "flex"
137
- },
138
- cascaderIsReady: false,
139
- };
140
- },
141
- computed: {
142
- // This computed property populates filter data's entry object with $data from this sidebar
143
- filterEntry: function() {
144
- return {
145
- numberOfHits: this.numberOfHits,
146
- filterFacets: this.filter
147
- };
148
- }
149
- },
150
- methods: {
151
- contextCardUpdate: function(val){
152
- this.contextCardEntry = val
153
- },
154
- resetSearch: function() {
155
- this.numberOfHits = 0
156
- this.discoverIds = []
157
- this._dois = []
158
- this.results = []
159
- this.loadingCards = false
160
- },
161
- openSearch: function(filter, search='') {
162
- this.searchInput = search;
163
- this.resetPageNavigation();
164
- //Proceed normally if cascader is ready
165
- if (this.cascaderIsReady) {
166
- this.filter = this.$refs.filtersRef.getValidatedFilters(filter);
167
- //Facets provided but cannot find at least one valid
168
- //facet. Tell the users the search is invalid and reset
169
- //facets check boxes.
170
- if ((filter && filter.length > 0) &&
171
- (this.filter && this.filter.length === 0)) {
172
- this.$refs.filtersRef.checkShowAllBoxes();
173
- this.resetSearch();
174
- } else if (this.filter) {
175
- this.searchAlgolia(this.filter, search);
176
- this.$refs.filtersRef.setCascader(this.filter);
177
- }
178
- } else {
179
- //cascader is not ready, perform search if no filter is set,
180
- //otherwise waith for cascader to be ready
181
- this.filter = filter;
182
- if (!filter || filter.length == 0) {
183
- this.searchAlgolia(this.filter, search);
184
- }
185
- }
186
- },
187
- addFilter: function(filter) {
188
- if (this.cascaderIsReady) {
189
- this.resetPageNavigation();
190
- if (filter) {
191
- if (this.$refs.filtersRef.addFilter(filter))
192
- this.$refs.filtersRef.initiateSearch();
193
- }
194
- } else {
195
- if (Array.isArray(this.filter)) {
196
- this.filter.push(filter);
197
- } else {
198
- this.filter = [filter];
199
- }
200
- }
201
- },
202
- cascaderReady: function() {
203
- this.cascaderIsReady = true;
204
- this.openSearch(this.filter, this.searchInput);
205
- },
206
- clearSearchClicked: function() {
207
- this.searchInput = "";
208
- this.resetPageNavigation();
209
- this.searchAlgolia(this.filters, this.searchInput);
210
- },
211
- searchEvent: function(event = false) {
212
- if (event.keyCode === 13 || event instanceof MouseEvent) {
213
- this.resetPageNavigation();
214
- this.searchAlgolia(this.filters, this.searchInput);
215
- }
216
- },
217
- filterUpdate: function(filters) {
218
- this.filters = [...filters]
219
- this.resetPageNavigation()
220
- this.searchAlgolia(filters, this.searchInput)
221
- this.$emit("search-changed", {
222
- value: filters,
223
- type: "filter-update"
224
- });
225
- },
226
- searchAlgolia(filters, query=''){
227
- // Algolia search
228
- this.loadingCards = true
229
- this.algoliaClient.anatomyInSearch(getFilters(filters), query).then(anatomy => {
230
- EventBus.$emit("anatomyFound", anatomy)
231
- })
232
- this.algoliaClient.search(getFilters(filters), query, this.numberPerPage, this.page).then(searchData => {
233
- this.numberOfHits = searchData.total
234
- this.discoverIds = searchData.discoverIds
235
- this._dois = searchData.dois
236
- this.results = searchData.items
237
- this.loadingCards = false
238
- this.scrollToTop()
239
- this.$emit("search-changed", { value: this.searchInput, type: "query-update" })
240
- if (this._abortController)
241
- this._abortController.abort()
242
- this._abortController = new AbortController()
243
- const signal = this._abortController.signal
244
- //Search ongoing, let the current flow progress
245
- this.perItemSearch(signal, { count: 0 })
246
- })
247
- },
248
- filtersLoading: function (val) {
249
- this.loadingCards = val;
250
- },
251
- numberPerPageUpdate: function(val) {
252
- this.numberPerPage = val;
253
- this.pageChange(1);
254
- },
255
- pageChange: function(page) {
256
- this.start = (page - 1) * this.numberPerPage;
257
- this.page = page
258
- this.searchAlgolia(this.filters, this.searchInput, this.numberPerPage, this.page)
259
- },
260
- handleMissingData: function(doi) {
261
- let i = this.results.findIndex(res=> res.doi === doi)
262
- if (this.results[i])
263
- this.results[i].detailsReady = true;
264
- },
265
- perItemSearch: function(signal, data) {
266
- //Maximum 10 downloads at once to prevent long waiting time
267
- //between unfinished search and new search
268
- const maxDownloads = 10;
269
- if (maxDownloads > data.count) {
270
- const doi = this._dois.shift();
271
- if (doi) {
272
- data.count++;
273
- this.callSciCrunch(this.envVars.API_LOCATION, {'dois': [doi]}, signal)
274
- .then(result => {
275
- if (result.numberOfHits === 0)
276
- this.handleMissingData(doi);
277
- else
278
- this.resultsProcessing(result);
279
- this.$refs.content.style["overflow-y"] = "scroll";
280
- data.count--;
281
- //Async::Download finished, get the next one
282
- this.perItemSearch(signal, data);
283
- })
284
- .catch(result => {
285
- if (result.name !== 'AbortError') {
286
- this.handleMissingData(doi);
287
- data.count--;
288
- //Async::Download not aborted, get the next one
289
- this.perItemSearch(signal, data);
290
- }
291
- });
292
- //Check and make another request until it gets to max downloads
293
- this.perItemSearch(signal, data);
294
- }
295
- }
296
- },
297
- scrollToTop: function() {
298
- if (this.$refs.content) {
299
- this.$refs.content.scroll({ top: 0, behavior: "smooth" });
300
- }
301
- },
302
- resetPageNavigation: function() {
303
- this.start = 0;
304
- this.page = 1;
305
- },
306
- resultsProcessing: function(data) {
307
- this.lastSearch = this.searchInput;
308
-
309
- if (data.results.length === 0) {
310
- return;
311
- }
312
- data.results.forEach(element => {
313
- // match the scicrunch result with algolia result
314
- let i = this.results.findIndex(res => element.doi ? element.doi.includes(res.doi) : false )
315
- // Assign scicrunch results to the object
316
- Object.assign(this.results[i], element)
317
- // Assign the attributes that need some processing
318
- Object.assign(this.results[i],{
319
- numberSamples: element.sampleSize
320
- ? parseInt(element.sampleSize)
321
- : 0,
322
- numberSubjects: element.subjectSize
323
- ? parseInt(element.subjectSize)
324
- : 0,
325
- updated: (element.updated && element.updated.length) > 0 ? element.updated[0].timestamp.split("T")[0] : "",
326
- url: element.uri[0],
327
- datasetId: element.dataset_identifier,
328
- datasetRevision: element.dataset_revision,
329
- datasetVersion: element.dataset_version,
330
- organs: (element.organs && element.organs.length > 0)
331
- ? [...new Set(element.organs.map(v => v.name))]
332
- : undefined,
333
- species: element.organisms
334
- ? element.organisms[0].species
335
- ? [...new Set(element.organisms.map((v) =>v.species ? v.species.name : null))]
336
- : undefined
337
- : undefined, // This processing only includes each gender once into 'sexes'
338
- scaffolds: element['abi-scaffold-metadata-file'],
339
- thumbnails: element['abi-thumbnail'] ? element['abi-thumbnail']: element['abi-scaffold-thumbnail'],
340
- scaffoldViews: element['abi-scaffold-view-file'],
341
- videos: element.video,
342
- plots: element['abi-plot'],
343
- images: element['common-images'],
344
- contextualInformation: element['abi-contextual-information'].length > 0 ? element['abi-contextual-information'] : undefined,
345
- segmentation: element['mbf-segmentation'],
346
- simulation: element['abi-simulation-file'],
347
- additionalLinks: element.additionalLinks,
348
- detailsReady: true,
349
- })
350
- Vue.set(this.results, i, this.results[i])
351
- });
352
- },
353
- createfilterParams: function(params) {
354
- let p = new URLSearchParams();
355
- //Check if field is array or value
356
- for (const key in params) {
357
- if (Array.isArray(params[key])) {
358
- params[key].forEach(e => {
359
- p.append(key, e);
360
- });
361
- } else {
362
- p.append(key, params[key]);
363
- }
364
- }
365
- return p.toString();
366
- },
367
- callSciCrunch: function(apiLocation, params = {}, signal) {
368
- return new Promise((resolve, reject) => {
369
- // Add parameters if we are sent them
370
- let fullEndpoint = this.envVars.API_LOCATION + this.searchEndpoint + "?" + this.createfilterParams(params);
371
- fetch(fullEndpoint, {signal})
372
- .then(handleErrors)
373
- .then(response => response.json())
374
- .then(data => resolve(data))
375
- .catch(data => reject(data));
376
- });
377
- },
378
- },
379
- mounted: function() {
380
- // initialise algolia
381
- this.algoliaClient = new AlgoliaClient(this.envVars.ALGOLIA_ID, this.envVars.ALGOLIA_KEY, this.envVars.PENNSIEVE_API_LOCATION);
382
- this.algoliaClient.initIndex(this.envVars.ALGOLIA_INDEX);
383
- this.openSearch(undefined, '');
384
- },
385
- created: function() {
386
- //Create non-reactive local variables
387
- this.searchEndpoint = "dataset_info/using_multiple_dois/";
388
- }
389
- };
390
- </script>
391
-
392
- <!-- Add "scoped" attribute to limit CSS to this component only -->
393
- <style scoped>
394
- .content-card {
395
- height: 100%;
396
- flex-flow: column;
397
- display: flex;
398
- }
399
-
400
- .button {
401
- background-color: #8300bf;
402
- border: #8300bf;
403
- color: white;
404
- }
405
-
406
- .step-item {
407
- font-size: 14px;
408
- margin-bottom: 18px;
409
- text-align: left;
410
- }
411
-
412
- .search-input {
413
- width: 298px !important;
414
- height: 40px;
415
- padding-right: 14px;
416
- align-items: left;
417
- }
418
-
419
- .header {
420
- border: solid 1px #292b66;
421
- background-color: #292b66;
422
- text-align: left;
423
- }
424
-
425
- .pagination {
426
- padding-bottom: 16px;
427
- background-color: white;
428
- text-align: center;
429
- }
430
-
431
- .pagination >>> button {
432
- background-color: white !important;
433
- }
434
- .pagination >>> li {
435
- background-color: white !important;
436
- }
437
- .pagination >>> li.active {
438
- color: #8300bf;
439
- }
440
-
441
- .error-feedback {
442
- font-family: Asap;
443
- font-size: 14px;
444
- font-style: italic;
445
- padding-top: 15px;
446
- }
447
-
448
- .content-card >>> .el-card__header {
449
- background-color: #292b66;
450
- border: solid 1px #292b66;
451
- }
452
-
453
- .content-card >>> .el-card__body {
454
- background-color: #f7faff;
455
- overflow-y: hidden;
456
- }
457
-
458
- .content {
459
- width: 518px;
460
- flex: 1 1 auto;
461
- box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.06);
462
- border: solid 1px #e4e7ed;
463
- background-color: #ffffff;
464
- overflow-y: scroll;
465
- scrollbar-width: thin;
466
- }
467
-
468
- .content >>> .el-loading-spinner .path {
469
- stroke: #8300bf;
470
- }
471
-
472
- .content >>> .step-item:first-child .seperator-path{
473
- display: none;
474
- }
475
-
476
- .content >>> .step-item:not(:first-child) .seperator-path{
477
- width: 486px;
478
- height: 0px;
479
- border: solid 1px #e4e7ed;
480
- background-color: #e4e7ed;
481
- }
482
-
483
- .scrollbar::-webkit-scrollbar-track {
484
- border-radius: 10px;
485
- background-color: #f5f5f5;
486
- }
487
-
488
- .scrollbar::-webkit-scrollbar {
489
- width: 12px;
490
- right: -12px;
491
- background-color: #f5f5f5;
492
- }
493
-
494
- .scrollbar::-webkit-scrollbar-thumb {
495
- border-radius: 4px;
496
- box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.06);
497
- background-color: #979797;
498
- }
499
-
500
- >>> .el-input__suffix {
501
- padding-right: 10px;
502
- }
503
-
504
- >>> .my-drawer {
505
- background: rgba(0, 0, 0, 0);
506
- box-shadow: none;
507
- }
508
- </style>
1
+ <template>
2
+ <el-card :body-style="bodyStyle" class="content-card">
3
+ <div slot="header" class="header">
4
+ <context-card v-if="contextCardEntry && contextCardEnabled" :entry="contextCardEntry" :envVars="envVars"/>
5
+ <el-input
6
+ class="search-input"
7
+ placeholder="Search"
8
+ v-model="searchInput"
9
+ @keyup.native="searchEvent"
10
+ clearable
11
+ @clear="clearSearchClicked"
12
+ ></el-input>
13
+ <el-button class="button" @click="searchEvent">Search</el-button>
14
+ </div>
15
+ <SearchFilters
16
+ class="filters"
17
+ ref="filtersRef"
18
+ :entry="filterEntry"
19
+ :envVars="envVars"
20
+ @filterResults="filterUpdate"
21
+ @numberPerPage="numberPerPageUpdate"
22
+ @loading="filtersLoading"
23
+ @cascaderReady="cascaderReady"
24
+ ></SearchFilters>
25
+ <div class="content scrollbar" v-loading="loadingCards" ref="content">
26
+ <div
27
+ class="error-feedback"
28
+ v-if="results.length === 0 && !loadingCards"
29
+ >No results found - Please change your search / filter criteria.</div>
30
+ <div v-for="result in results" :key="result.doi" class="step-item">
31
+ <DatasetCard :entry="result" :envVars="envVars" @contextUpdate="contextCardUpdate"></DatasetCard>
32
+ </div>
33
+ <el-pagination
34
+ class="pagination"
35
+ :current-page.sync="page"
36
+ hide-on-single-page
37
+ large
38
+ layout="prev, pager, next"
39
+ :page-size="numberPerPage"
40
+ :total="numberOfHits"
41
+ @current-change="pageChange"
42
+ ></el-pagination>
43
+ </div>
44
+ </el-card>
45
+ </template>
46
+
47
+
48
+ <script>
49
+ /* eslint-disable no-alert, no-console */
50
+ import Vue from "vue";
51
+ import {
52
+ Button,
53
+ Card,
54
+ Drawer,
55
+ Icon,
56
+ Input,
57
+ Loading,
58
+ Pagination
59
+ } from "element-ui";
60
+ import lang from "element-ui/lib/locale/lang/en";
61
+ import locale from "element-ui/lib/locale";
62
+ import SearchFilters from "./SearchFilters";
63
+ import DatasetCard from "./DatasetCard";
64
+ import ContextCard from "./ContextCard.vue";
65
+ import EventBus from "./EventBus"
66
+
67
+ import {AlgoliaClient} from "../algolia/algolia.js";
68
+ import {getFilters} from "../algolia/utils.js"
69
+
70
+ locale.use(lang);
71
+ Vue.use(Button);
72
+ Vue.use(Card);
73
+ Vue.use(Drawer);
74
+ Vue.use(Icon);
75
+ Vue.use(Input);
76
+ Vue.use(Loading);
77
+ Vue.use(Pagination);
78
+
79
+ // handleErrors: A custom fetch error handler to recieve messages from the server
80
+ // even when an error is found
81
+ var handleErrors = async function(response) {
82
+ if (!response.ok) {
83
+ let parse = await response.json();
84
+ if (parse) {
85
+ throw new Error(parse.message);
86
+ } else {
87
+ throw new Error(response);
88
+ }
89
+ }
90
+ return response;
91
+ };
92
+
93
+ var initial_state = {
94
+ searchInput: "",
95
+ lastSearch: "",
96
+ results: [],
97
+ numberOfHits: 0,
98
+ filter: [],
99
+ loadingCards: false,
100
+ numberPerPage: 10,
101
+ page: 1,
102
+ pageModel: 1,
103
+ start: 0,
104
+ hasSearched: false,
105
+ contextCardEntry: undefined,
106
+ contextCardEnabled: true,
107
+ };
108
+
109
+ export default {
110
+ components: { SearchFilters, DatasetCard, ContextCard },
111
+ name: "SideBarContent",
112
+ props: {
113
+ visible: {
114
+ type: Boolean,
115
+ default: false
116
+ },
117
+ isDrawer: {
118
+ type: Boolean,
119
+ default: true
120
+ },
121
+ entry: {
122
+ type: Object,
123
+ default: () => initial_state
124
+ },
125
+ envVars: {
126
+ type: Object,
127
+ default: () => {}
128
+ },
129
+ },
130
+ data: function() {
131
+ return {
132
+ ...this.entry,
133
+ bodyStyle: {
134
+ flex: "1 1 auto",
135
+ "flex-flow": "column",
136
+ display: "flex"
137
+ },
138
+ cascaderIsReady: false,
139
+ };
140
+ },
141
+ computed: {
142
+ // This computed property populates filter data's entry object with $data from this sidebar
143
+ filterEntry: function() {
144
+ return {
145
+ numberOfHits: this.numberOfHits,
146
+ filterFacets: this.filter
147
+ };
148
+ }
149
+ },
150
+ methods: {
151
+ contextCardUpdate: function(val){
152
+ this.contextCardEntry = val
153
+ },
154
+ resetSearch: function() {
155
+ this.numberOfHits = 0
156
+ this.discoverIds = []
157
+ this._dois = []
158
+ this.results = []
159
+ this.loadingCards = false
160
+ },
161
+ openSearch: function(filter, search='') {
162
+ this.searchInput = search;
163
+ this.resetPageNavigation();
164
+ //Proceed normally if cascader is ready
165
+ if (this.cascaderIsReady) {
166
+ this.filter = this.$refs.filtersRef.getValidatedFilters(filter);
167
+ //Facets provided but cannot find at least one valid
168
+ //facet. Tell the users the search is invalid and reset
169
+ //facets check boxes.
170
+ if ((filter && filter.length > 0) &&
171
+ (this.filter && this.filter.length === 0)) {
172
+ this.$refs.filtersRef.checkShowAllBoxes();
173
+ this.resetSearch();
174
+ } else if (this.filter) {
175
+ this.searchAlgolia(this.filter, search);
176
+ this.$refs.filtersRef.setCascader(this.filter);
177
+ }
178
+ } else {
179
+ //cascader is not ready, perform search if no filter is set,
180
+ //otherwise waith for cascader to be ready
181
+ this.filter = filter;
182
+ if (!filter || filter.length == 0) {
183
+ this.searchAlgolia(this.filter, search);
184
+ }
185
+ }
186
+ },
187
+ addFilter: function(filter) {
188
+ if (this.cascaderIsReady) {
189
+ this.resetPageNavigation();
190
+ if (filter) {
191
+ if (this.$refs.filtersRef.addFilter(filter))
192
+ this.$refs.filtersRef.initiateSearch();
193
+ }
194
+ } else {
195
+ if (Array.isArray(this.filter)) {
196
+ this.filter.push(filter);
197
+ } else {
198
+ this.filter = [filter];
199
+ }
200
+ }
201
+ },
202
+ cascaderReady: function() {
203
+ this.cascaderIsReady = true;
204
+ this.openSearch(this.filter, this.searchInput);
205
+ },
206
+ clearSearchClicked: function() {
207
+ this.searchInput = "";
208
+ this.resetPageNavigation();
209
+ this.searchAlgolia(this.filters, this.searchInput);
210
+ },
211
+ searchEvent: function(event = false) {
212
+ if (event.keyCode === 13 || event instanceof MouseEvent) {
213
+ this.resetPageNavigation();
214
+ this.searchAlgolia(this.filters, this.searchInput);
215
+ }
216
+ },
217
+ filterUpdate: function(filters) {
218
+ this.filters = [...filters]
219
+ this.resetPageNavigation()
220
+ this.searchAlgolia(filters, this.searchInput)
221
+ this.$emit("search-changed", {
222
+ value: filters,
223
+ type: "filter-update"
224
+ });
225
+ },
226
+ searchAlgolia(filters, query=''){
227
+ // Algolia search
228
+ this.loadingCards = true
229
+ this.algoliaClient.anatomyInSearch(getFilters(filters), query).then(anatomy => {
230
+ EventBus.$emit("anatomyFound", anatomy)
231
+ })
232
+ this.algoliaClient.search(getFilters(filters), query, this.numberPerPage, this.page).then(searchData => {
233
+ this.numberOfHits = searchData.total
234
+ this.discoverIds = searchData.discoverIds
235
+ this._dois = searchData.dois
236
+ this.results = searchData.items
237
+ this.loadingCards = false
238
+ this.scrollToTop()
239
+ this.$emit("search-changed", { value: this.searchInput, type: "query-update" })
240
+ if (this._abortController)
241
+ this._abortController.abort()
242
+ this._abortController = new AbortController()
243
+ const signal = this._abortController.signal
244
+ //Search ongoing, let the current flow progress
245
+ this.perItemSearch(signal, { count: 0 })
246
+ })
247
+ },
248
+ filtersLoading: function (val) {
249
+ this.loadingCards = val;
250
+ },
251
+ numberPerPageUpdate: function(val) {
252
+ this.numberPerPage = val;
253
+ this.pageChange(1);
254
+ },
255
+ pageChange: function(page) {
256
+ this.start = (page - 1) * this.numberPerPage;
257
+ this.page = page
258
+ this.searchAlgolia(this.filters, this.searchInput, this.numberPerPage, this.page)
259
+ },
260
+ handleMissingData: function(doi) {
261
+ let i = this.results.findIndex(res=> res.doi === doi)
262
+ if (this.results[i])
263
+ this.results[i].detailsReady = true;
264
+ },
265
+ perItemSearch: function(signal, data) {
266
+ //Maximum 10 downloads at once to prevent long waiting time
267
+ //between unfinished search and new search
268
+ const maxDownloads = 10;
269
+ if (maxDownloads > data.count) {
270
+ const doi = this._dois.shift();
271
+ if (doi) {
272
+ data.count++;
273
+ this.callSciCrunch(this.envVars.API_LOCATION, {'dois': [doi]}, signal)
274
+ .then(result => {
275
+ if (result.numberOfHits === 0)
276
+ this.handleMissingData(doi);
277
+ else
278
+ this.resultsProcessing(result);
279
+ this.$refs.content.style["overflow-y"] = "scroll";
280
+ data.count--;
281
+ //Async::Download finished, get the next one
282
+ this.perItemSearch(signal, data);
283
+ })
284
+ .catch(result => {
285
+ if (result.name !== 'AbortError') {
286
+ this.handleMissingData(doi);
287
+ data.count--;
288
+ //Async::Download not aborted, get the next one
289
+ this.perItemSearch(signal, data);
290
+ }
291
+ });
292
+ //Check and make another request until it gets to max downloads
293
+ this.perItemSearch(signal, data);
294
+ }
295
+ }
296
+ },
297
+ scrollToTop: function() {
298
+ if (this.$refs.content) {
299
+ this.$refs.content.scroll({ top: 0, behavior: "smooth" });
300
+ }
301
+ },
302
+ resetPageNavigation: function() {
303
+ this.start = 0;
304
+ this.page = 1;
305
+ },
306
+ resultsProcessing: function(data) {
307
+ this.lastSearch = this.searchInput;
308
+
309
+ if (data.results.length === 0) {
310
+ return;
311
+ }
312
+ data.results.forEach(element => {
313
+ // match the scicrunch result with algolia result
314
+ let i = this.results.findIndex(res => element.doi ? element.doi.includes(res.doi) : false )
315
+ // Assign scicrunch results to the object
316
+ Object.assign(this.results[i], element)
317
+ // Assign the attributes that need some processing
318
+ Object.assign(this.results[i],{
319
+ numberSamples: element.sampleSize
320
+ ? parseInt(element.sampleSize)
321
+ : 0,
322
+ numberSubjects: element.subjectSize
323
+ ? parseInt(element.subjectSize)
324
+ : 0,
325
+ updated: (element.updated && element.updated.length) > 0 ? element.updated[0].timestamp.split("T")[0] : "",
326
+ url: element.uri[0],
327
+ datasetId: element.dataset_identifier,
328
+ datasetRevision: element.dataset_revision,
329
+ datasetVersion: element.dataset_version,
330
+ organs: (element.organs && element.organs.length > 0)
331
+ ? [...new Set(element.organs.map(v => v.name))]
332
+ : undefined,
333
+ species: element.organisms
334
+ ? element.organisms[0].species
335
+ ? [...new Set(element.organisms.map((v) =>v.species ? v.species.name : null))]
336
+ : undefined
337
+ : undefined, // This processing only includes each gender once into 'sexes'
338
+ scaffolds: element['abi-scaffold-metadata-file'],
339
+ thumbnails: element['abi-thumbnail'] ? element['abi-thumbnail']: element['abi-scaffold-thumbnail'],
340
+ scaffoldViews: element['abi-scaffold-view-file'],
341
+ videos: element.video,
342
+ plots: element['abi-plot'],
343
+ images: element['common-images'],
344
+ contextualInformation: element['abi-contextual-information'].length > 0 ? element['abi-contextual-information'] : undefined,
345
+ segmentation: element['mbf-segmentation'],
346
+ simulation: element['abi-simulation-file'],
347
+ additionalLinks: element.additionalLinks,
348
+ detailsReady: true,
349
+ })
350
+ Vue.set(this.results, i, this.results[i])
351
+ });
352
+ },
353
+ createfilterParams: function(params) {
354
+ let p = new URLSearchParams();
355
+ //Check if field is array or value
356
+ for (const key in params) {
357
+ if (Array.isArray(params[key])) {
358
+ params[key].forEach(e => {
359
+ p.append(key, e);
360
+ });
361
+ } else {
362
+ p.append(key, params[key]);
363
+ }
364
+ }
365
+ return p.toString();
366
+ },
367
+ callSciCrunch: function(apiLocation, params = {}, signal) {
368
+ return new Promise((resolve, reject) => {
369
+ // Add parameters if we are sent them
370
+ let fullEndpoint = this.envVars.API_LOCATION + this.searchEndpoint + "?" + this.createfilterParams(params);
371
+ fetch(fullEndpoint, {signal})
372
+ .then(handleErrors)
373
+ .then(response => response.json())
374
+ .then(data => resolve(data))
375
+ .catch(data => reject(data));
376
+ });
377
+ },
378
+ },
379
+ mounted: function() {
380
+ // initialise algolia
381
+ this.algoliaClient = new AlgoliaClient(this.envVars.ALGOLIA_ID, this.envVars.ALGOLIA_KEY, this.envVars.PENNSIEVE_API_LOCATION);
382
+ this.algoliaClient.initIndex(this.envVars.ALGOLIA_INDEX);
383
+ this.openSearch(this.filter, this.searchInput);
384
+ },
385
+ created: function() {
386
+ //Create non-reactive local variables
387
+ this.searchEndpoint = "dataset_info/using_multiple_dois/";
388
+ }
389
+ };
390
+ </script>
391
+
392
+ <!-- Add "scoped" attribute to limit CSS to this component only -->
393
+ <style scoped>
394
+ .content-card {
395
+ height: 100%;
396
+ flex-flow: column;
397
+ display: flex;
398
+ }
399
+
400
+ .button {
401
+ background-color: #8300bf;
402
+ border: #8300bf;
403
+ color: white;
404
+ }
405
+
406
+ .step-item {
407
+ font-size: 14px;
408
+ margin-bottom: 18px;
409
+ text-align: left;
410
+ }
411
+
412
+ .search-input {
413
+ width: 298px !important;
414
+ height: 40px;
415
+ padding-right: 14px;
416
+ align-items: left;
417
+ }
418
+
419
+ .header {
420
+ border: solid 1px #292b66;
421
+ background-color: #292b66;
422
+ text-align: left;
423
+ }
424
+
425
+ .pagination {
426
+ padding-bottom: 16px;
427
+ background-color: white;
428
+ text-align: center;
429
+ }
430
+
431
+ .pagination >>> button {
432
+ background-color: white !important;
433
+ }
434
+ .pagination >>> li {
435
+ background-color: white !important;
436
+ }
437
+ .pagination >>> li.active {
438
+ color: #8300bf;
439
+ }
440
+
441
+ .error-feedback {
442
+ font-family: Asap;
443
+ font-size: 14px;
444
+ font-style: italic;
445
+ padding-top: 15px;
446
+ }
447
+
448
+ .content-card >>> .el-card__header {
449
+ background-color: #292b66;
450
+ border: solid 1px #292b66;
451
+ }
452
+
453
+ .content-card >>> .el-card__body {
454
+ background-color: #f7faff;
455
+ overflow-y: hidden;
456
+ }
457
+
458
+ .content {
459
+ width: 518px;
460
+ flex: 1 1 auto;
461
+ box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.06);
462
+ border: solid 1px #e4e7ed;
463
+ background-color: #ffffff;
464
+ overflow-y: scroll;
465
+ scrollbar-width: thin;
466
+ }
467
+
468
+ .content >>> .el-loading-spinner .path {
469
+ stroke: #8300bf;
470
+ }
471
+
472
+ .content >>> .step-item:first-child .seperator-path{
473
+ display: none;
474
+ }
475
+
476
+ .content >>> .step-item:not(:first-child) .seperator-path{
477
+ width: 486px;
478
+ height: 0px;
479
+ border: solid 1px #e4e7ed;
480
+ background-color: #e4e7ed;
481
+ }
482
+
483
+ .scrollbar::-webkit-scrollbar-track {
484
+ border-radius: 10px;
485
+ background-color: #f5f5f5;
486
+ }
487
+
488
+ .scrollbar::-webkit-scrollbar {
489
+ width: 12px;
490
+ right: -12px;
491
+ background-color: #f5f5f5;
492
+ }
493
+
494
+ .scrollbar::-webkit-scrollbar-thumb {
495
+ border-radius: 4px;
496
+ box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.06);
497
+ background-color: #979797;
498
+ }
499
+
500
+ >>> .el-input__suffix {
501
+ padding-right: 10px;
502
+ }
503
+
504
+ >>> .my-drawer {
505
+ background: rgba(0, 0, 0, 0);
506
+ box-shadow: none;
507
+ }
508
+ </style>