@abi-software/map-side-bar 2.2.0 → 2.2.1-alpha-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.
@@ -1,576 +1,576 @@
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
- this.loadingCards = true
252
- this.algoliaClient
253
- .anatomyInSearch(getFilters(filters), query)
254
- .then((anatomy) => {
255
- EventBus.emit('available-facets', {
256
- uberons: anatomy,
257
- labels: this.algoliaClient.anatomyFacetLabels,
258
- })
259
- })
260
- this.algoliaClient
261
- .search(getFilters(filters), query, this.numberPerPage, this.page)
262
- .then((searchData) => {
263
- this.numberOfHits = searchData.total
264
- this.discoverIds = searchData.discoverIds
265
- this._dois = searchData.dois
266
- this.results = searchData.items
267
- this.loadingCards = false
268
- this.scrollToTop()
269
- this.$emit('search-changed', {
270
- value: this.searchInput,
271
- type: 'query-update',
272
- })
273
- if (this._abortController) this._abortController.abort()
274
- this._abortController = new AbortController()
275
- const signal = this._abortController.signal
276
- //Search ongoing, let the current flow progress
277
- this.perItemSearch(signal, { count: 0 })
278
- })
279
- },
280
- filtersLoading: function (val) {
281
- this.loadingCards = val
282
- },
283
- numberPerPageUpdate: function (val) {
284
- this.numberPerPage = val
285
- this.pageChange(1)
286
- },
287
- pageChange: function (page) {
288
- this.start = (page - 1) * this.numberPerPage
289
- this.page = page
290
- this.searchAlgolia(
291
- this.filters,
292
- this.searchInput,
293
- this.numberPerPage,
294
- this.page
295
- )
296
- },
297
- handleMissingData: function (doi) {
298
- let i = this.results.findIndex((res) => res.doi === doi)
299
- if (this.results[i]) this.results[i].detailsReady = true
300
- },
301
- perItemSearch: function (signal, data) {
302
- //Maximum 10 downloads at once to prevent long waiting time
303
- //between unfinished search and new search
304
- const maxDownloads = 10
305
- if (maxDownloads > data.count) {
306
- const doi = this._dois.shift()
307
- if (doi) {
308
- data.count++
309
- this.callSciCrunch(this.envVars.API_LOCATION, { dois: [doi] }, signal)
310
- .then((result) => {
311
- if (result.numberOfHits === 0) this.handleMissingData(doi)
312
- else this.resultsProcessing(result)
313
- this.$refs.content.style['overflow-y'] = 'scroll'
314
- data.count--
315
- //Async::Download finished, get the next one
316
- this.perItemSearch(signal, data)
317
- })
318
- .catch((result) => {
319
- if (result.name !== 'AbortError') {
320
- this.handleMissingData(doi)
321
- data.count--
322
- //Async::Download not aborted, get the next one
323
- this.perItemSearch(signal, data)
324
- }
325
- })
326
- //Check and make another request until it gets to max downloads
327
- this.perItemSearch(signal, data)
328
- }
329
- }
330
- },
331
- scrollToTop: function () {
332
- if (this.$refs.content) {
333
- this.$refs.content.scroll({ top: 0, behavior: 'smooth' })
334
- }
335
- },
336
- resetPageNavigation: function () {
337
- this.start = 0
338
- this.page = 1
339
- },
340
- resultsProcessing: function (data) {
341
- this.lastSearch = this.searchInput
342
-
343
- if (data.results.length === 0) {
344
- return
345
- }
346
- data.results.forEach((element) => {
347
- // match the scicrunch result with algolia result
348
- let i = this.results.findIndex((res) =>
349
- element.doi ? element.doi.includes(res.doi) : false
350
- )
351
- // Assign scicrunch results to the object
352
- Object.assign(this.results[i], element)
353
- // Assign the attributes that need some processing
354
- Object.assign(this.results[i], {
355
- numberSamples: element.sampleSize ? parseInt(element.sampleSize) : 0,
356
- numberSubjects: element.subjectSize
357
- ? parseInt(element.subjectSize)
358
- : 0,
359
- updated:
360
- (element.updated && element.updated.length) > 0
361
- ? element.updated[0].timestamp.split('T')[0]
362
- : '',
363
- url: element.uri[0],
364
- datasetId: element.dataset_identifier,
365
- datasetRevision: element.dataset_revision,
366
- datasetVersion: element.dataset_version,
367
- organs:
368
- element.organs && element.organs.length > 0
369
- ? [...new Set(element.organs.map((v) => v.name))]
370
- : undefined,
371
- species: element.organisms
372
- ? element.organisms[0].species
373
- ? [
374
- ...new Set(
375
- element.organisms.map((v) =>
376
- v.species ? v.species.name : null
377
- )
378
- ),
379
- ]
380
- : undefined
381
- : undefined, // This processing only includes each gender once into 'sexes'
382
- scaffolds: element['abi-scaffold-metadata-file'],
383
- thumbnails: element['abi-thumbnail']
384
- ? element['abi-thumbnail']
385
- : element['abi-scaffold-thumbnail'],
386
- scaffoldViews: element['abi-scaffold-view-file'],
387
- videos: element.video,
388
- plots: element['abi-plot'],
389
- images: element['common-images'],
390
- contextualInformation:
391
- element['abi-contextual-information'].length > 0
392
- ? element['abi-contextual-information']
393
- : undefined,
394
- segmentation: element['mbf-segmentation'],
395
- simulation: element['abi-simulation-file'],
396
- additionalLinks: element.additionalLinks,
397
- detailsReady: true,
398
- })
399
- this.results[i] = this.results[i]
400
- })
401
- },
402
- createfilterParams: function (params) {
403
- let p = new URLSearchParams()
404
- //Check if field is array or value
405
- for (const key in params) {
406
- if (Array.isArray(params[key])) {
407
- params[key].forEach((e) => {
408
- p.append(key, e)
409
- })
410
- } else {
411
- p.append(key, params[key])
412
- }
413
- }
414
- return p.toString()
415
- },
416
- callSciCrunch: function (apiLocation, params = {}, signal) {
417
- return new Promise((resolve, reject) => {
418
- // Add parameters if we are sent them
419
- let fullEndpoint =
420
- this.envVars.API_LOCATION +
421
- this.searchEndpoint +
422
- '?' +
423
- this.createfilterParams(params)
424
- fetch(fullEndpoint, { signal })
425
- .then(handleErrors)
426
- .then((response) => response.json())
427
- .then((data) => resolve(data))
428
- .catch((data) => reject(data))
429
- })
430
- },
431
- getAlgoliaFacets: async function () {
432
- let facets = await this.algoliaClient.getAlgoliaFacets(
433
- facetPropPathMapping
434
- )
435
- return facets
436
- },
437
- searchHistorySearch: function (item) {
438
- this.searchInput = item.search
439
- this.filters = item.filters
440
- this.openSearch(item.filters, item.search)
441
- },
442
- },
443
- mounted: function () {
444
- // initialise algolia
445
- this.algoliaClient = new AlgoliaClient(
446
- this.envVars.ALGOLIA_ID,
447
- this.envVars.ALGOLIA_KEY,
448
- this.envVars.PENNSIEVE_API_LOCATION
449
- )
450
- this.algoliaClient.initIndex(this.envVars.ALGOLIA_INDEX)
451
- this.openSearch(this.filter, this.searchInput)
452
- },
453
- created: function () {
454
- //Create non-reactive local variables
455
- this.searchEndpoint = 'dataset_info/using_multiple_dois/'
456
- },
457
- }
458
- </script>
459
-
460
- <style lang="scss" scoped>
461
- .dataset-card:hover {
462
- border-style: solid;
463
- border-color: var(--el-color-primary);
464
- border-radius: 5px;
465
- }
466
-
467
- .content-card {
468
- height: 100%;
469
- flex-flow: column;
470
- display: flex;
471
- }
472
-
473
- .step-item {
474
- font-size: 14px;
475
- margin-bottom: 18px;
476
- text-align: left;
477
- }
478
-
479
- .search-input {
480
- width: 298px !important;
481
- height: 40px;
482
- padding-right: 14px;
483
- align-items: left;
484
- }
485
-
486
- .header {
487
- border: solid 1px #292b66;
488
- background-color: #292b66;
489
- text-align: left;
490
- }
491
-
492
- .pagination {
493
- padding-bottom: 16px;
494
- background-color: white;
495
- padding-left: 95px;
496
- font-weight: bold;
497
- }
498
-
499
- .pagination :deep(button) {
500
- background-color: white !important;
501
- }
502
- .pagination :deep(li) {
503
- background-color: white !important;
504
- }
505
- .pagination :deep(li.is-active) {
506
- color: $app-primary-color;
507
- }
508
-
509
- .error-feedback {
510
- font-family: Asap;
511
- font-size: 14px;
512
- font-style: italic;
513
- padding-top: 15px;
514
- }
515
-
516
- .content-card :deep(.el-card__header) {
517
- background-color: #292b66;
518
- border: solid 1px #292b66;
519
- }
520
-
521
- .content-card :deep(.el-card__body) {
522
- background-color: #f7faff;
523
- overflow-y: hidden;
524
- }
525
-
526
- .content {
527
- width: 515px;
528
- flex: 1 1 auto;
529
- box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.06);
530
- border: solid 1px #e4e7ed;
531
- background-color: #ffffff;
532
- overflow-y: scroll;
533
- scrollbar-width: thin;
534
- }
535
-
536
- .content :deep(.el-loading-spinner .path) {
537
- stroke: $app-primary-color;
538
- }
539
-
540
- .content :deep(.step-item:first-child .seperator-path) {
541
- display: none;
542
- }
543
-
544
- .content :deep(.step-item:not(:first-child) .seperator-path) {
545
- width: 455px;
546
- height: 0px;
547
- border: solid 1px #e4e7ed;
548
- background-color: #e4e7ed;
549
- }
550
-
551
- .scrollbar::-webkit-scrollbar-track {
552
- border-radius: 10px;
553
- background-color: #f5f5f5;
554
- }
555
-
556
- .scrollbar::-webkit-scrollbar {
557
- width: 12px;
558
- right: -12px;
559
- background-color: #f5f5f5;
560
- }
561
-
562
- .scrollbar::-webkit-scrollbar-thumb {
563
- border-radius: 4px;
564
- box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.06);
565
- background-color: #979797;
566
- }
567
-
568
- :deep(.el-input__suffix) {
569
- padding-right: 0px;
570
- }
571
-
572
- :deep(.my-drawer) {
573
- background: rgba(0, 0, 0, 0);
574
- box-shadow: none;
575
- }
576
- </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
254
+ .anatomyInSearch(getFilters(filters), query)
255
+ .then((r) => {
256
+ // Send result anatomy to the scaffold and flatmap
257
+ EventBus.emit('anatomy-in-datasets', r.forFlatmap)
258
+ EventBus.emit('number-of-datasets-for-anatomies', r.forScaffold)
259
+ })
260
+ this.algoliaClient
261
+ .search(getFilters(filters), query, this.numberPerPage, this.page)
262
+ .then((searchData) => {
263
+ this.numberOfHits = searchData.total
264
+ this.discoverIds = searchData.discoverIds
265
+ this._dois = searchData.dois
266
+ this.results = searchData.items
267
+ this.loadingCards = false
268
+ this.scrollToTop()
269
+ this.$emit('search-changed', {
270
+ value: this.searchInput,
271
+ type: 'query-update',
272
+ })
273
+ if (this._abortController) this._abortController.abort()
274
+ this._abortController = new AbortController()
275
+ const signal = this._abortController.signal
276
+ //Search ongoing, let the current flow progress
277
+ this.perItemSearch(signal, { count: 0 })
278
+ })
279
+ },
280
+ filtersLoading: function (val) {
281
+ this.loadingCards = val
282
+ },
283
+ numberPerPageUpdate: function (val) {
284
+ this.numberPerPage = val
285
+ this.pageChange(1)
286
+ },
287
+ pageChange: function (page) {
288
+ this.start = (page - 1) * this.numberPerPage
289
+ this.page = page
290
+ this.searchAlgolia(
291
+ this.filters,
292
+ this.searchInput,
293
+ this.numberPerPage,
294
+ this.page
295
+ )
296
+ },
297
+ handleMissingData: function (doi) {
298
+ let i = this.results.findIndex((res) => res.doi === doi)
299
+ if (this.results[i]) this.results[i].detailsReady = true
300
+ },
301
+ perItemSearch: function (signal, data) {
302
+ //Maximum 10 downloads at once to prevent long waiting time
303
+ //between unfinished search and new search
304
+ const maxDownloads = 10
305
+ if (maxDownloads > data.count) {
306
+ const doi = this._dois.shift()
307
+ if (doi) {
308
+ data.count++
309
+ this.callSciCrunch(this.envVars.API_LOCATION, { dois: [doi] }, signal)
310
+ .then((result) => {
311
+ if (result.numberOfHits === 0) this.handleMissingData(doi)
312
+ else this.resultsProcessing(result)
313
+ this.$refs.content.style['overflow-y'] = 'scroll'
314
+ data.count--
315
+ //Async::Download finished, get the next one
316
+ this.perItemSearch(signal, data)
317
+ })
318
+ .catch((result) => {
319
+ if (result.name !== 'AbortError') {
320
+ this.handleMissingData(doi)
321
+ data.count--
322
+ //Async::Download not aborted, get the next one
323
+ this.perItemSearch(signal, data)
324
+ }
325
+ })
326
+ //Check and make another request until it gets to max downloads
327
+ this.perItemSearch(signal, data)
328
+ }
329
+ }
330
+ },
331
+ scrollToTop: function () {
332
+ if (this.$refs.content) {
333
+ this.$refs.content.scroll({ top: 0, behavior: 'smooth' })
334
+ }
335
+ },
336
+ resetPageNavigation: function () {
337
+ this.start = 0
338
+ this.page = 1
339
+ },
340
+ resultsProcessing: function (data) {
341
+ this.lastSearch = this.searchInput
342
+
343
+ if (data.results.length === 0) {
344
+ return
345
+ }
346
+ data.results.forEach((element) => {
347
+ // match the scicrunch result with algolia result
348
+ let i = this.results.findIndex((res) =>
349
+ element.doi ? element.doi.includes(res.doi) : false
350
+ )
351
+ // Assign scicrunch results to the object
352
+ Object.assign(this.results[i], element)
353
+ // Assign the attributes that need some processing
354
+ Object.assign(this.results[i], {
355
+ numberSamples: element.sampleSize ? parseInt(element.sampleSize) : 0,
356
+ numberSubjects: element.subjectSize
357
+ ? parseInt(element.subjectSize)
358
+ : 0,
359
+ updated:
360
+ (element.updated && element.updated.length) > 0
361
+ ? element.updated[0].timestamp.split('T')[0]
362
+ : '',
363
+ url: element.uri[0],
364
+ datasetId: element.dataset_identifier,
365
+ datasetRevision: element.dataset_revision,
366
+ datasetVersion: element.dataset_version,
367
+ organs:
368
+ element.organs && element.organs.length > 0
369
+ ? [...new Set(element.organs.map((v) => v.name))]
370
+ : undefined,
371
+ species: element.organisms
372
+ ? element.organisms[0].species
373
+ ? [
374
+ ...new Set(
375
+ element.organisms.map((v) =>
376
+ v.species ? v.species.name : null
377
+ )
378
+ ),
379
+ ]
380
+ : undefined
381
+ : undefined, // This processing only includes each gender once into 'sexes'
382
+ scaffolds: element['abi-scaffold-metadata-file'],
383
+ thumbnails: element['abi-thumbnail']
384
+ ? element['abi-thumbnail']
385
+ : element['abi-scaffold-thumbnail'],
386
+ scaffoldViews: element['abi-scaffold-view-file'],
387
+ videos: element.video,
388
+ plots: element['abi-plot'],
389
+ images: element['common-images'],
390
+ contextualInformation:
391
+ element['abi-contextual-information'].length > 0
392
+ ? element['abi-contextual-information']
393
+ : undefined,
394
+ segmentation: element['mbf-segmentation'],
395
+ simulation: element['abi-simulation-file'],
396
+ additionalLinks: element.additionalLinks,
397
+ detailsReady: true,
398
+ })
399
+ this.results[i] = this.results[i]
400
+ })
401
+ },
402
+ createfilterParams: function (params) {
403
+ let p = new URLSearchParams()
404
+ //Check if field is array or value
405
+ for (const key in params) {
406
+ if (Array.isArray(params[key])) {
407
+ params[key].forEach((e) => {
408
+ p.append(key, e)
409
+ })
410
+ } else {
411
+ p.append(key, params[key])
412
+ }
413
+ }
414
+ return p.toString()
415
+ },
416
+ callSciCrunch: function (apiLocation, params = {}, signal) {
417
+ return new Promise((resolve, reject) => {
418
+ // Add parameters if we are sent them
419
+ let fullEndpoint =
420
+ this.envVars.API_LOCATION +
421
+ this.searchEndpoint +
422
+ '?' +
423
+ this.createfilterParams(params)
424
+ fetch(fullEndpoint, { signal })
425
+ .then(handleErrors)
426
+ .then((response) => response.json())
427
+ .then((data) => resolve(data))
428
+ .catch((data) => reject(data))
429
+ })
430
+ },
431
+ getAlgoliaFacets: async function () {
432
+ let facets = await this.algoliaClient.getAlgoliaFacets(
433
+ facetPropPathMapping
434
+ )
435
+ return facets
436
+ },
437
+ searchHistorySearch: function (item) {
438
+ this.searchInput = item.search
439
+ this.filters = item.filters
440
+ this.openSearch(item.filters, item.search)
441
+ },
442
+ },
443
+ mounted: function () {
444
+ // initialise algolia
445
+ this.algoliaClient = new AlgoliaClient(
446
+ this.envVars.ALGOLIA_ID,
447
+ this.envVars.ALGOLIA_KEY,
448
+ this.envVars.PENNSIEVE_API_LOCATION
449
+ )
450
+ this.algoliaClient.initIndex(this.envVars.ALGOLIA_INDEX)
451
+ this.openSearch(this.filter, this.searchInput)
452
+ },
453
+ created: function () {
454
+ //Create non-reactive local variables
455
+ this.searchEndpoint = 'dataset_info/using_multiple_dois/'
456
+ },
457
+ }
458
+ </script>
459
+
460
+ <style lang="scss" scoped>
461
+ .dataset-card:hover {
462
+ border-style: solid;
463
+ border-color: var(--el-color-primary);
464
+ border-radius: 5px;
465
+ }
466
+
467
+ .content-card {
468
+ height: 100%;
469
+ flex-flow: column;
470
+ display: flex;
471
+ }
472
+
473
+ .step-item {
474
+ font-size: 14px;
475
+ margin-bottom: 18px;
476
+ text-align: left;
477
+ }
478
+
479
+ .search-input {
480
+ width: 298px !important;
481
+ height: 40px;
482
+ padding-right: 14px;
483
+ align-items: left;
484
+ }
485
+
486
+ .header {
487
+ border: solid 1px #292b66;
488
+ background-color: #292b66;
489
+ text-align: left;
490
+ }
491
+
492
+ .pagination {
493
+ padding-bottom: 16px;
494
+ background-color: white;
495
+ padding-left: 95px;
496
+ font-weight: bold;
497
+ }
498
+
499
+ .pagination :deep(button) {
500
+ background-color: white !important;
501
+ }
502
+ .pagination :deep(li) {
503
+ background-color: white !important;
504
+ }
505
+ .pagination :deep(li.is-active) {
506
+ color: $app-primary-color;
507
+ }
508
+
509
+ .error-feedback {
510
+ font-family: Asap;
511
+ font-size: 14px;
512
+ font-style: italic;
513
+ padding-top: 15px;
514
+ }
515
+
516
+ .content-card :deep(.el-card__header) {
517
+ background-color: #292b66;
518
+ border: solid 1px #292b66;
519
+ }
520
+
521
+ .content-card :deep(.el-card__body) {
522
+ background-color: #f7faff;
523
+ overflow-y: hidden;
524
+ }
525
+
526
+ .content {
527
+ width: 515px;
528
+ flex: 1 1 auto;
529
+ box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.06);
530
+ border: solid 1px #e4e7ed;
531
+ background-color: #ffffff;
532
+ overflow-y: scroll;
533
+ scrollbar-width: thin;
534
+ }
535
+
536
+ .content :deep(.el-loading-spinner .path) {
537
+ stroke: $app-primary-color;
538
+ }
539
+
540
+ .content :deep(.step-item:first-child .seperator-path) {
541
+ display: none;
542
+ }
543
+
544
+ .content :deep(.step-item:not(:first-child) .seperator-path) {
545
+ width: 455px;
546
+ height: 0px;
547
+ border: solid 1px #e4e7ed;
548
+ background-color: #e4e7ed;
549
+ }
550
+
551
+ .scrollbar::-webkit-scrollbar-track {
552
+ border-radius: 10px;
553
+ background-color: #f5f5f5;
554
+ }
555
+
556
+ .scrollbar::-webkit-scrollbar {
557
+ width: 12px;
558
+ right: -12px;
559
+ background-color: #f5f5f5;
560
+ }
561
+
562
+ .scrollbar::-webkit-scrollbar-thumb {
563
+ border-radius: 4px;
564
+ box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.06);
565
+ background-color: #979797;
566
+ }
567
+
568
+ :deep(.el-input__suffix) {
569
+ padding-right: 0px;
570
+ }
571
+
572
+ :deep(.my-drawer) {
573
+ background: rgba(0, 0, 0, 0);
574
+ box-shadow: none;
575
+ }
576
+ </style>