@abi-software/map-side-bar 1.5.4 → 1.6.0

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 +229 -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 +255 -231
  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 +255 -231
  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 +209 -209
  17. package/src/algolia/utils.js +101 -101
  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/DatasetCard.vue +328 -328
  23. package/src/components/EventBus.js +3 -3
  24. package/src/components/ImageGallery.vue +537 -531
  25. package/src/components/SearchFilters.vue +703 -703
  26. package/src/components/SearchHistory.vue +147 -147
  27. package/src/components/SideBar.vue +235 -235
  28. package/src/components/SidebarContent.vue +564 -564
  29. package/src/components/Tabs.vue +78 -78
  30. package/src/components/index.js +8 -8
  31. package/src/components/species-map.js +8 -8
  32. package/src/main.js +8 -8
  33. package/src/mixins/S3Bucket.vue +37 -31
  34. package/static.json +6 -6
  35. package/vue.config.js +19 -19
  36. package/src/components/ContextCard.vue +0 -397
  37. package/src/components/hardcoded-context-info.js +0 -80
@@ -1,564 +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
- <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.getHierarchicalValidatedFilters(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>
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.getHierarchicalValidatedFilters(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>