@abi-software/map-side-bar 1.5.0 → 1.5.2

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