@abi-software/map-side-bar 2.1.0 → 2.2.1-alpha-1

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