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