@abi-software/map-side-bar 2.4.0-alpha-1 → 2.4.0-isan-0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/.eslintrc.js +12 -12
  2. package/.postcssrc.json +5 -5
  3. package/LICENSE +201 -201
  4. package/README.md +168 -168
  5. package/cypress.config.js +23 -23
  6. package/dist/map-side-bar.js +9362 -15112
  7. package/dist/map-side-bar.umd.cjs +103 -50
  8. package/dist/style.css +1 -1
  9. package/package.json +77 -77
  10. package/reporter-config.json +9 -9
  11. package/src/App.vue +268 -266
  12. package/src/algolia/algolia.js +256 -255
  13. package/src/algolia/utils.js +105 -100
  14. package/src/assets/_variables.scss +43 -43
  15. package/src/assets/styles.scss +6 -6
  16. package/src/components/BadgesGroup.vue +124 -124
  17. package/src/components/ConnectivityInfo.vue +619 -619
  18. package/src/components/DatasetCard.vue +367 -367
  19. package/src/components/EventBus.js +3 -3
  20. package/src/components/ExternalResourceCard.vue +113 -113
  21. package/src/components/ImageGallery.vue +542 -542
  22. package/src/components/PMRDatasetCard.vue +303 -237
  23. package/src/components/SearchFilters.vue +1023 -1023
  24. package/src/components/SearchHistory.vue +175 -175
  25. package/src/components/SideBar.vue +456 -436
  26. package/src/components/SidebarContent.vue +710 -730
  27. package/src/components/Tabs.vue +145 -145
  28. package/src/components/index.js +8 -8
  29. package/src/components/species-map.js +8 -8
  30. package/src/components.d.ts +0 -1
  31. package/src/exampleConnectivityInput.js +291 -291
  32. package/src/flatmapQueries/flatmapQueries.js +220 -168
  33. package/src/main.js +9 -9
  34. package/src/mixins/S3Bucket.vue +37 -37
  35. package/src/mixins/mixedPageCalculation.vue +102 -78
  36. package/static.json +6 -6
  37. package/vite.config.js +55 -55
  38. package/vuese-generator.js +65 -65
  39. package/dist/data/pmr-sample.json +0 -3181
  40. package/public/data/pmr-sample.json +0 -3181
  41. package/src/components/FlatmapDatasetCard.vue +0 -171
  42. package/src/components/allPaths.js +0 -5928
  43. package/src/components/pmrTest.js +0 -4
@@ -1,730 +1,710 @@
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-select
14
- v-model="mode"
15
- class="data-type-select"
16
- placeholder="Search over..."
17
- @change="onSelectValueChange"
18
- >
19
- <el-option
20
- v-for="item in selectOptions"
21
- :key="item.value"
22
- :label="item.label"
23
- :value="item.value"
24
- />
25
- </el-select>
26
- </div>
27
- </template>
28
- <SearchFilters
29
- class="filters"
30
- ref="filtersRef"
31
- :entry="filterEntry"
32
- :envVars="envVars"
33
- @filterResults="filterUpdate"
34
- @numberPerPage="numberPerPageUpdate"
35
- @loading="filtersLoading"
36
- @cascaderReady="cascaderReady"
37
- ></SearchFilters>
38
- <SearchHistory
39
- ref="searchHistory"
40
- @search="searchHistorySearch"
41
- ></SearchHistory>
42
- pmr hits: {{ pmrNumberOfHits }}
43
- <div class="content scrollbar" v-loading="loadingCards" ref="content">
44
- <div class="error-feedback" v-if="results.length === 0 && !loadingCards">
45
- No results found - Please change your search / filter criteria.
46
- </div>
47
- <div v-for="(result, i) in results" :key="i" class="step-item">
48
- <DatasetCard
49
- v-if="result.dataSource === 'SPARC'"
50
- class="dataset-card"
51
- :entry="result"
52
- :envVars="envVars"
53
- @mouseenter="hoverChanged(result)"
54
- @mouseleave="hoverChanged(undefined)"
55
- ></DatasetCard>
56
- <PMRDatasetCard
57
- v-else-if="result.dataSource === 'PMR'"
58
- class="dataset-card"
59
- :entry="result"
60
- :envVars="envVars"
61
- @mouseenter="hoverChanged(result)"
62
- @mouseleave="hoverChanged(undefined)"
63
- ></PMRDatasetCard>
64
- <flatmap-dataset-card
65
- v-else-if="result.dataSource === 'Flatmap'"
66
- class="dataset-card"
67
- :entry="result"
68
- :envVars="envVars"
69
- @mouseenter="hoverChanged(result)"
70
- @mouseleave="hoverChanged(undefined)"
71
- ></flatmap-dataset-card>
72
- </div>
73
- <el-pagination
74
- class="pagination"
75
- v-model:current-page="page"
76
- hide-on-single-page
77
- large
78
- layout="prev, pager, next"
79
- :page-size="numberPerPage"
80
- :total="numberOfHits"
81
- @current-change="pageChange"
82
- ></el-pagination>
83
- </div>
84
- </el-card>
85
- </template>
86
-
87
- <script>
88
- /* eslint-disable no-alert, no-console */
89
- import {
90
- ElButton as Button,
91
- ElCard as Card,
92
- ElDrawer as Drawer,
93
- ElIcon as Icon,
94
- ElInput as Input,
95
- ElPagination as Pagination,
96
- } from 'element-plus'
97
- import SearchFilters from './SearchFilters.vue'
98
- import SearchHistory from './SearchHistory.vue'
99
- import EventBus from './EventBus.js'
100
-
101
- import DatasetCard from "./DatasetCard.vue";
102
- import PMRDatasetCard from "./PMRDatasetCard.vue";
103
- import FlatmapDatasetCard from './FlatmapDatasetCard.vue';
104
-
105
- import allPaths from './allPaths.js'
106
-
107
- import { AlgoliaClient } from '../algolia/algolia.js'
108
- import { getFilters, facetPropPathMapping } from '../algolia/utils.js'
109
- import FlatmapQueries from '../flatmapQueries/flatmapQueries.js'
110
- import mixedPageCalculation from '../mixins/mixedPageCalculation.vue'
111
-
112
- // TODO: to update API URL
113
- const API_URL = "/data/pmr-sample.json";
114
- const RatioOfPMRResults = 0.2; // Ratio of PMR results to Sparc results
115
-
116
- // handleErrors: A custom fetch error handler to recieve messages from the server
117
- // even when an error is found
118
- var handleErrors = async function (response) {
119
- if (!response.ok) {
120
- let parse = await response.json()
121
- if (parse) {
122
- throw new Error(parse.message)
123
- } else {
124
- throw new Error(response)
125
- }
126
- }
127
- return response
128
- }
129
-
130
- var initial_state = {
131
- searchInput: '',
132
- lastSearch: '',
133
- results: [],
134
- pmrNumberOfHits: 0,
135
- sparcNumberOfHits: 0,
136
- variableRatio: RatioOfPMRResults,
137
- filter: [],
138
- loadingCards: false,
139
- numberPerPage: 10,
140
- page: 1,
141
- pmrResultsOnlyFlag: false,
142
- hasSearched: false,
143
- contextCardEnabled: false,
144
- pmrResults: [],
145
- mode: 'Sparc Datasets',
146
- allPaths: allPaths.values,
147
- selectOptions: [
148
- { value: "Sparc Datasets", label: "Sparc Datasets" },
149
- { value: "PMR", label: "PMR" },
150
- { value: "Flatmap", label: "Flatmap"}
151
- ],
152
- };
153
-
154
-
155
-
156
- export default {
157
- components: {
158
- SearchFilters,
159
- DatasetCard,
160
- PMRDatasetCard,
161
- FlatmapDatasetCard,
162
- SearchHistory,
163
- Button,
164
- Card,
165
- Drawer,
166
- Icon,
167
- Input,
168
- Pagination
169
- },
170
- name: 'SideBarContent',
171
- mixins: [mixedPageCalculation],
172
- props: {
173
- visible: {
174
- type: Boolean,
175
- default: false,
176
- },
177
- isDrawer: {
178
- type: Boolean,
179
- default: true,
180
- },
181
- entry: {
182
- type: Object,
183
- default: () => initial_state,
184
- },
185
- envVars: {
186
- type: Object,
187
- default: () => {},
188
- },
189
- },
190
- data: function () {
191
- return {
192
- ...this.entry,
193
- bodyStyle: {
194
- flex: '1 1 auto',
195
- 'flex-flow': 'column',
196
- display: 'flex',
197
- },
198
- cascaderIsReady: false,
199
- }
200
- },
201
- computed: {
202
- // This computed property populates filter data's entry object with $data from this sidebar
203
- filterEntry: function () {
204
- return {
205
- numberOfHits: this.numberOfHits,
206
- filterFacets: this.filter,
207
- }
208
- },
209
- // npp_SPARC: Number per page for SPARC datasets
210
- npp_SPARC: function () {
211
- return Math.round(this.numberPerPage * (1 - RatioOfPMRResults))
212
- },
213
- // npp_PMR: Number per page for PMR datasets
214
- npp_PMR: function () {
215
- return Math.round(this.numberPerPage * RatioOfPMRResults)
216
- },
217
- numberOfHits: function () {
218
- return this.sparcNumberOfHits + this.pmrNumberOfHits
219
- },
220
-
221
- },
222
- methods: {
223
- hoverChanged: function (data) {
224
- this.$emit('hover-changed', data)
225
- },
226
- resetSearch: function () {
227
- this.pmrNumberOfHits = 0
228
- this.sparcNumberOfHits = 0
229
- this.page = 1
230
- this.calculateVariableRatio()
231
- this.discoverIds = []
232
- this._dois = []
233
- this.results = []
234
- this.loadingCards = false
235
- },
236
- // openSearch: Resets the results, populates dataset cards and filters. Will use Algolia and SciCrunch data uness pmr mode is set
237
- openSearch: function(filter, search = '', resetSearch = true) {
238
- if (resetSearch) {
239
- this.resetSearch();
240
- this.openAlgoliaSearch(filter, search);
241
- } else {
242
- this.searchAlgolia(filter, search);
243
- }
244
- this.openPMRSearch(filter, search)
245
- },
246
-
247
- // openPMRSearch: Resets the results, populates dataset cards and filters with PMR data.
248
- openPMRSearch: function (filter, search = '') {
249
- this.flatmapQueries.updateOffset(this.calculatePMROffest())
250
- this.flatmapQueries.updateLimit(this.PMRLimit(this.pmrResultsOnlyFlag))
251
- this.flatmapQueries.pmrSearch(filter, search).then((data) => {
252
- data.forEach((result) => {
253
- this.results.push(result)
254
- })
255
- this.pmrNumberOfHits = this.flatmapQueries.numberOfHits
256
- })
257
- },
258
-
259
- // openAlgoliaSearch: Resets the results, populates dataset cards and filters with Algloia and SciCrunch data.
260
- openAlgoliaSearch: function (filter, search = '') {
261
- this.searchInput = search
262
- //Proceed normally if cascader is ready
263
- if (this.cascaderIsReady) {
264
- this.filter =
265
- this.$refs.filtersRef.getHierarchicalValidatedFilters(filter)
266
- //Facets provided but cannot find at least one valid
267
- //facet. Tell the users the search is invalid and reset
268
- //facets check boxes.
269
- if (
270
- filter &&
271
- filter.length > 0 &&
272
- this.filter &&
273
- this.filter.length === 0
274
- ) {
275
- this.$refs.filtersRef.checkShowAllBoxes()
276
- this.resetSearch()
277
- } else if (this.filter) {
278
- this.searchAlgolia(this.filter, search)
279
- this.$refs.filtersRef.setCascader(this.filter)
280
- }
281
- } else {
282
- //cascader is not ready, perform search if no filter is set,
283
- //otherwise waith for cascader to be ready
284
- this.filter = filter
285
- if (!filter || filter.length == 0) {
286
- this.searchAlgolia(this.filter, search)
287
- }
288
- }
289
- },
290
- addFilter: function (filter) {
291
- if (this.cascaderIsReady) {
292
- this.resetSearch()
293
- if (filter) {
294
- if (this.$refs.filtersRef.addFilter(filter))
295
- this.$refs.filtersRef.initiateSearch()
296
- }
297
- } else {
298
- if (Array.isArray(this.filter)) {
299
- this.filter.push(filter)
300
- } else {
301
- this.filter = [filter]
302
- }
303
- }
304
- },
305
- cascaderReady: function () {
306
- this.cascaderIsReady = true
307
- this.openSearch(this.filter, this.searchInput)
308
- },
309
- clearSearchClicked: function () {
310
- this.searchInput = ''
311
- this.resetSearch()
312
- this.openSearch(this.filter, this.searchInput)
313
- this.$refs.searchHistory.selectValue = 'Full search history'
314
- },
315
- searchEvent: function (event = false) {
316
- if (event.keyCode === 13 || event instanceof MouseEvent) {
317
- this.openSearch(this.filter, this.searchInput)
318
- this.$refs.searchHistory.selectValue = 'Full search history'
319
- this.$refs.searchHistory.addSearchToHistory(
320
- this.filters,
321
- this.searchInput
322
- )
323
- }
324
- },
325
- updatePMROnlyFlag: function (filters) {
326
- const pmrSearchObject = filters.find((tmp) => tmp.facet === 'PMR');
327
- if (pmrSearchObject) {
328
- this.pmrResultsOnlyFlag = true
329
- } else {
330
- this.pmrResultsOnlyFlag = false
331
- }
332
- },
333
- filterUpdate: function (filters) {
334
- this.filters = [...filters]
335
-
336
- // Check if PMR is in the filters
337
- this.updatePMROnlyFlag(filters)
338
-
339
- // Note that we cannot use the openSearch function as that modifies filters
340
- this.resetSearch()
341
- this.searchAlgolia(filters, this.searchInput)
342
- this.openPMRSearch(filters, this.searchInput)
343
- this.$emit('search-changed', {
344
- value: filters,
345
- type: 'filter-update',
346
- })
347
- },
348
- searchAlgolia(filters, query = '') {
349
-
350
- // Remove loading if we dont expect any results
351
- if (this.SPARCLimit() === 0) {
352
- this.loadingCards = false
353
- return
354
- }
355
-
356
- // Algolia search
357
-
358
- this.loadingCards = true
359
- this.algoliaClient
360
- .anatomyInSearch(getFilters(filters), query)
361
- .then((r) => {
362
- // Send result anatomy to the scaffold and flatmap
363
- EventBus.emit('anatomy-in-datasets', r.forFlatmap)
364
- EventBus.emit('number-of-datasets-for-anatomies', r.forScaffold)
365
- })
366
- this.algoliaClient
367
- .search(getFilters(filters), query, this.calculateSPARCOffest(), this.SPARCLimit(this.pmrResultsOnlyFlag) )
368
- .then((searchData) => {
369
- this.sparcNumberOfHits = searchData.total
370
- this.discoverIds = searchData.discoverIds
371
- this._dois = searchData.dois
372
- searchData.items.forEach((item) => {
373
- item.detailsReady = false
374
- this.results.push(item)
375
- })
376
- // add the items to the results
377
- this.results.concat(searchData.items)
378
- this.loadingCards = false
379
- this.scrollToTop()
380
- this.$emit('search-changed', {
381
- value: this.searchInput,
382
- type: 'query-update',
383
- })
384
- if (this._abortController) this._abortController.abort()
385
- this._abortController = new AbortController()
386
- const signal = this._abortController.signal
387
- //Search ongoing, let the current flow progress
388
- this.perItemSearch(signal, { count: 0 })
389
- })
390
- },
391
- filtersLoading: function (val) {
392
- this.loadingCards = val
393
- },
394
- numberPerPageUpdate: function (val) {
395
- this.numberPerPage = val
396
- this.pageChange(1)
397
- },
398
- pageChange: function (page) {
399
- this.page = page
400
- this.results = []
401
- this.calculateVariableRatio()
402
- this.openSearch(this.filter, this.searchInput, false)
403
- },
404
- handleMissingData: function (doi) {
405
- let i = this.results.findIndex((res) => res.doi === doi)
406
- if (this.results[i]) this.results[i].detailsReady = true
407
- },
408
- perItemSearch: function (signal, data) {
409
- //Maximum 10 downloads at once to prevent long waiting time
410
- //between unfinished search and new search
411
- const maxDownloads = 10
412
- if (maxDownloads > data.count) {
413
- const doi = this._dois.shift()
414
- if (doi) {
415
- data.count++
416
- this.callSciCrunch(this.envVars.API_LOCATION, { dois: [doi] }, signal)
417
- .then((result) => {
418
- if (result.numberOfHits === 0) this.handleMissingData(doi)
419
- else this.resultsProcessing(result)
420
- this.$refs.content.style['overflow-y'] = 'scroll'
421
- data.count--
422
- //Async::Download finished, get the next one
423
- this.perItemSearch(signal, data)
424
- })
425
- .catch((result) => {
426
- if (result.name !== 'AbortError') {
427
- this.handleMissingData(doi)
428
- data.count--
429
- //Async::Download not aborted, get the next one
430
- this.perItemSearch(signal, data)
431
- }
432
- })
433
- //Check and make another request until it gets to max downloads
434
- this.perItemSearch(signal, data)
435
- }
436
- }
437
- },
438
- scrollToTop: function () {
439
- if (this.$refs.content) {
440
- this.$refs.content.scroll({ top: 0, behavior: 'smooth' })
441
- }
442
- },
443
- resetPageNavigation: function () {
444
- this.page = 1
445
- },
446
- resultsProcessing: function (data) {
447
- this.lastSearch = this.searchInput
448
-
449
- if (data.results.length === 0) {
450
- return
451
- }
452
- data.results.forEach((element) => {
453
- // match the scicrunch result with algolia result
454
- let i = this.results.findIndex((res) =>
455
- element.doi ? element.doi.includes(res.doi) : false
456
- )
457
- // Assign scicrunch results to the object
458
- Object.assign(this.results[i], element)
459
- // Assign the attributes that need some processing
460
- Object.assign(this.results[i], {
461
- numberSamples: element.sampleSize ? parseInt(element.sampleSize) : 0,
462
- numberSubjects: element.subjectSize
463
- ? parseInt(element.subjectSize)
464
- : 0,
465
- updated:
466
- (element.updated && element.updated.length) > 0
467
- ? element.updated[0].timestamp.split('T')[0]
468
- : '',
469
- url: element.uri[0],
470
- datasetId: element.dataset_identifier,
471
- datasetRevision: element.dataset_revision,
472
- datasetVersion: element.dataset_version,
473
- organs:
474
- element.organs && element.organs.length > 0
475
- ? [...new Set(element.organs.map((v) => v.name))]
476
- : undefined,
477
- species: element.organisms
478
- ? element.organisms[0].species
479
- ? [
480
- ...new Set(
481
- element.organisms.map((v) =>
482
- v.species ? v.species.name : null
483
- )
484
- ),
485
- ]
486
- : undefined
487
- : undefined, // This processing only includes each gender once into 'sexes'
488
- scaffolds: element['abi-scaffold-metadata-file'],
489
- thumbnails: element['abi-thumbnail']
490
- ? element['abi-thumbnail']
491
- : element['abi-scaffold-thumbnail'],
492
- scaffoldViews: element['abi-scaffold-view-file'],
493
- videos: element.video,
494
- plots: element['abi-plot'],
495
- images: element['common-images'],
496
- contextualInformation:
497
- element['abi-contextual-information'].length > 0
498
- ? element['abi-contextual-information']
499
- : undefined,
500
- segmentation: element['mbf-segmentation'],
501
- simulation: element['abi-simulation-file'],
502
- additionalLinks: element.additionalLinks,
503
- detailsReady: true,
504
- })
505
- this.results[i] = this.results[i]
506
- })
507
- },
508
- createfilterParams: function (params) {
509
- let p = new URLSearchParams()
510
- //Check if field is array or value
511
- for (const key in params) {
512
- if (Array.isArray(params[key])) {
513
- params[key].forEach((e) => {
514
- p.append(key, e)
515
- })
516
- } else {
517
- p.append(key, params[key])
518
- }
519
- }
520
- return p.toString()
521
- },
522
- callSciCrunch: function (apiLocation, params = {}, signal) {
523
- return new Promise((resolve, reject) => {
524
- // Add parameters if we are sent them
525
- let fullEndpoint =
526
- this.envVars.API_LOCATION +
527
- this.searchEndpoint +
528
- '?' +
529
- this.createfilterParams(params)
530
- fetch(fullEndpoint, { signal })
531
- .then(handleErrors)
532
- .then((response) => response.json())
533
- .then((data) => resolve(data))
534
- .catch((data) => reject(data))
535
- })
536
- },
537
- getAlgoliaFacets: async function () {
538
- let facets = await this.algoliaClient.getAlgoliaFacets(
539
- facetPropPathMapping
540
- )
541
- return facets
542
- },
543
- searchHistorySearch: function (item) {
544
- this.searchInput = item.search
545
- this.filters = item.filters
546
- this.openSearch(item.filters, item.search)
547
- },
548
- onSelectValueChange: function (val) {
549
- this.mode = val
550
- this.openSearch(this.filter, this.searchInput, val)
551
- },
552
- },
553
- mounted: function () {
554
- // initialise algolia
555
- this.algoliaClient = new AlgoliaClient(
556
- this.envVars.ALGOLIA_ID,
557
- this.envVars.ALGOLIA_KEY,
558
- this.envVars.PENNSIEVE_API_LOCATION
559
- )
560
- this.algoliaClient.initIndex(this.envVars.ALGOLIA_INDEX)
561
-
562
- // initialise flatmap queries
563
- this.flatmapQueries = new FlatmapQueries()
564
- this.flatmapQueries.initialise(this.envVars.FLATMAP_API_LOCATION)
565
-
566
- // open search
567
- this.openSearch(this.filter, this.searchInput, this.mode )
568
- },
569
- created: function () {
570
- //Create non-reactive local variables
571
- this.searchEndpoint = 'dataset_info/using_multiple_dois/'
572
- },
573
- }
574
- </script>
575
-
576
- <style lang="scss" scoped>
577
- .dataset-card {
578
- position: relative;
579
-
580
- &::before {
581
- content: "";
582
- display: block;
583
- width: calc(100% - 15px);
584
- height: 100%;
585
- position: absolute;
586
- top: 7px;
587
- left: 7px;
588
- border-style: solid;
589
- border-radius: 5px;
590
- border-color: transparent;
591
- }
592
-
593
- &:hover {
594
- &::before {
595
- border-color: var(--el-color-primary);
596
- }
597
- }
598
- }
599
-
600
- .content-card {
601
- height: 100%;
602
- flex-flow: column;
603
- display: flex;
604
- }
605
-
606
- .data-type-select {
607
- width: 90px;
608
- margin-left: 10px;
609
- }
610
-
611
- .button {
612
- background-color: $app-primary-color;
613
- border: $app-primary-color;
614
- color: white;
615
- }
616
-
617
- .step-item {
618
- font-size: 14px;
619
- margin-bottom: 18px;
620
- text-align: left;
621
- }
622
-
623
- .search-input {
624
- width: 298px !important;
625
- height: 40px;
626
- padding-right: 14px;
627
- align-items: left;
628
- }
629
-
630
- .header {
631
- border: solid 1px #292b66;
632
- background-color: #292b66;
633
- text-align: left;
634
-
635
- .el-button {
636
- &:hover,
637
- &:focus {
638
- background: $app-primary-color;
639
- box-shadow: -3px 2px 4px #00000040;
640
- color: #fff;
641
- }
642
- }
643
- }
644
-
645
- .pagination {
646
- padding-bottom: 16px;
647
- background-color: white;
648
- padding-left: 95px;
649
- font-weight: bold;
650
- }
651
-
652
- .pagination :deep(button) {
653
- background-color: white !important;
654
- }
655
- .pagination :deep(li) {
656
- background-color: white !important;
657
- }
658
- .pagination :deep(li.is-active) {
659
- color: $app-primary-color;
660
- }
661
-
662
- .error-feedback {
663
- font-family: Asap;
664
- font-size: 14px;
665
- font-style: italic;
666
- padding-top: 15px;
667
- }
668
-
669
- .content-card :deep(.el-card__header) {
670
- background-color: #292b66;
671
- padding: 1rem;
672
- }
673
-
674
- .content-card :deep(.el-card__body) {
675
- background-color: #f7faff;
676
- overflow-y: hidden;
677
- padding: 1rem;
678
- }
679
-
680
- .content {
681
- // width: 515px;
682
- flex: 1 1 auto;
683
- box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.06);
684
- border: solid 1px #e4e7ed;
685
- background-color: #ffffff;
686
- overflow-y: scroll;
687
- scrollbar-width: thin;
688
- }
689
-
690
- .content :deep(.el-loading-spinner .path) {
691
- stroke: $app-primary-color;
692
- }
693
-
694
- .content :deep(.step-item:first-child .seperator-path) {
695
- display: none;
696
- }
697
-
698
- .content :deep(.step-item:not(:first-child) .seperator-path) {
699
- width: 455px;
700
- height: 0px;
701
- border: solid 1px #e4e7ed;
702
- background-color: #e4e7ed;
703
- }
704
-
705
- .scrollbar::-webkit-scrollbar-track {
706
- border-radius: 10px;
707
- background-color: #f5f5f5;
708
- }
709
-
710
- .scrollbar::-webkit-scrollbar {
711
- width: 12px;
712
- right: -12px;
713
- background-color: #f5f5f5;
714
- }
715
-
716
- .scrollbar::-webkit-scrollbar-thumb {
717
- border-radius: 4px;
718
- box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.06);
719
- background-color: #979797;
720
- }
721
-
722
- :deep(.el-input__suffix) {
723
- padding-right: 0px;
724
- }
725
-
726
- :deep(.my-drawer) {
727
- background: rgba(0, 0, 0, 0);
728
- box-shadow: none;
729
- }
730
- </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
+ pmr hits: {{ pmrNumberOfHits }}
38
+ <div class="content scrollbar" v-loading="loadingCards" ref="content">
39
+ <div class="error-feedback" v-if="results.length === 0 && !loadingCards">
40
+ No results found - Please change your search / filter criteria.
41
+ </div>
42
+ <div v-for="(result, i) in results" :key="result.doi || i" class="step-item">
43
+ <DatasetCard
44
+ v-if="result.dataSource === 'SPARC'"
45
+ class="dataset-card"
46
+ :entry="result"
47
+ :envVars="envVars"
48
+ @mouseenter="hoverChanged(result)"
49
+ @mouseleave="hoverChanged(undefined)"
50
+ ></DatasetCard>
51
+ <PMRDatasetCard
52
+ v-else-if="result.dataSource === 'PMR'"
53
+ class="dataset-card"
54
+ :entry="result"
55
+ :envVars="envVars"
56
+ @flatmap-clicked="onFlatmapClicked"
57
+ @simulation-clicked="onSimulationClicked"
58
+ @mouseenter="hoverChanged(result)"
59
+ @mouseleave="hoverChanged(undefined)"
60
+ ></PMRDatasetCard>
61
+ </div>
62
+ <el-pagination
63
+ class="pagination"
64
+ v-model:current-page="page"
65
+ hide-on-single-page
66
+ large
67
+ layout="prev, pager, next"
68
+ :page-size="numberPerPage"
69
+ :total="numberOfHits"
70
+ @current-change="pageChange"
71
+ ></el-pagination>
72
+ </div>
73
+ </el-card>
74
+ </template>
75
+
76
+ <script>
77
+ /* eslint-disable no-alert, no-console */
78
+ import {
79
+ ElButton as Button,
80
+ ElCard as Card,
81
+ ElDrawer as Drawer,
82
+ ElIcon as Icon,
83
+ ElInput as Input,
84
+ ElPagination as Pagination,
85
+ } from 'element-plus'
86
+ import SearchFilters from './SearchFilters.vue'
87
+ import SearchHistory from './SearchHistory.vue'
88
+ import EventBus from './EventBus.js'
89
+
90
+ import DatasetCard from "./DatasetCard.vue";
91
+ import PMRDatasetCard from "./PMRDatasetCard.vue";
92
+
93
+ import { AlgoliaClient } from '../algolia/algolia.js'
94
+ import { getFilters, facetPropPathMapping } from '../algolia/utils.js'
95
+ import FlatmapQueries from '../flatmapQueries/flatmapQueries.js'
96
+ import mixedPageCalculation from '../mixins/mixedPageCalculation.vue'
97
+
98
+ const RatioOfPMRResults = 0.2; // Ratio of PMR results to Sparc results
99
+
100
+ // handleErrors: A custom fetch error handler to recieve messages from the server
101
+ // even when an error is found
102
+ var handleErrors = async function (response) {
103
+ if (!response.ok) {
104
+ let parse = await response.json()
105
+ if (parse) {
106
+ throw new Error(parse.message)
107
+ } else {
108
+ throw new Error(response)
109
+ }
110
+ }
111
+ return response
112
+ }
113
+
114
+ var initial_state = {
115
+ searchInput: '',
116
+ lastSearch: '',
117
+ results: [],
118
+ pmrNumberOfHits: 0,
119
+ sparcNumberOfHits: 0,
120
+ filter: [],
121
+ loadingCards: false,
122
+ numberPerPage: 10,
123
+ page: 1,
124
+ pmrResultsOnlyFlag: false,
125
+ hasSearched: false,
126
+ contextCardEnabled: false,
127
+ pmrResults: [],
128
+ RatioOfPMRResults: RatioOfPMRResults,
129
+ };
130
+
131
+
132
+
133
+ export default {
134
+ components: {
135
+ SearchFilters,
136
+ DatasetCard,
137
+ PMRDatasetCard,
138
+ SearchHistory,
139
+ Button,
140
+ Card,
141
+ Drawer,
142
+ Icon,
143
+ Input,
144
+ Pagination
145
+ },
146
+ name: 'SideBarContent',
147
+ mixins: [mixedPageCalculation],
148
+ props: {
149
+ visible: {
150
+ type: Boolean,
151
+ default: false,
152
+ },
153
+ isDrawer: {
154
+ type: Boolean,
155
+ default: true,
156
+ },
157
+ entry: {
158
+ type: Object,
159
+ default: () => initial_state,
160
+ },
161
+ envVars: {
162
+ type: Object,
163
+ default: () => {},
164
+ },
165
+ },
166
+ data: function () {
167
+ return {
168
+ ...this.entry,
169
+ bodyStyle: {
170
+ flex: '1 1 auto',
171
+ 'flex-flow': 'column',
172
+ display: 'flex',
173
+ },
174
+ cascaderIsReady: false,
175
+ }
176
+ },
177
+ computed: {
178
+ // This computed property populates filter data's entry object with $data from this sidebar
179
+ filterEntry: function () {
180
+ return {
181
+ numberOfHits: this.numberOfHits,
182
+ filterFacets: this.filter,
183
+ }
184
+ },
185
+ // npp_SPARC: Number per page for SPARC datasets
186
+ npp_SPARC: function () {
187
+ return Math.round(this.numberPerPage * (1 - RatioOfPMRResults))
188
+ },
189
+ // npp_PMR: Number per page for PMR datasets
190
+ npp_PMR: function () {
191
+ return Math.round(this.numberPerPage * RatioOfPMRResults)
192
+ },
193
+ numberOfHits: function () {
194
+ return this.sparcNumberOfHits + this.pmrNumberOfHits
195
+ },
196
+
197
+ },
198
+ methods: {
199
+ hoverChanged: function (data) {
200
+ this.$emit('hover-changed', data)
201
+ },
202
+ onFlatmapClicked: function (data) {
203
+ this.$emit('flatmap-clicked', data);
204
+ },
205
+ onSimulationClicked: function (payload) {
206
+ this.$emit('simulation-clicked', payload)
207
+ },
208
+ // resetSearch: Resets the results, and page, and variable results ratio
209
+ // Does not: reset filters, search input, or search history
210
+ resetSearch: function () {
211
+ this.pmrNumberOfHits = 0
212
+ this.sparcNumberOfHits = 0
213
+ this.page = 1
214
+ this.calculateVariableRatio()
215
+ this.discoverIds = []
216
+ this._dois = []
217
+ this.results = []
218
+ this.loadingCards = false
219
+ },
220
+ // openSearch: Resets the results, populates dataset cards and filters. Will use Algolia and SciCrunch data uness pmr mode is set
221
+ openSearch: function(filter, search = '', resetSearch = true) {
222
+ if (resetSearch) {
223
+ this.resetSearch();
224
+ this.openAlgoliaSearch(filter, search);
225
+ } else {
226
+ this.searchAlgolia(filter, search);
227
+ }
228
+ this.openPMRSearch(filter, search)
229
+ },
230
+
231
+ // openPMRSearch: Resets the results, populates dataset cards and filters with PMR data.
232
+ openPMRSearch: function (filter, search = '') {
233
+ this.flatmapQueries.updateOffset(this.calculatePMROffest())
234
+ this.flatmapQueries.updateLimit(this.PMRLimit(this.pmrResultsOnlyFlag))
235
+ this.flatmapQueries.pmrSearch(filter, search).then((data) => {
236
+ data.forEach((result) => {
237
+ this.results.push(result)
238
+ })
239
+ this.pmrNumberOfHits = this.flatmapQueries.numberOfHits
240
+ })
241
+ },
242
+
243
+ // openAlgoliaSearch: Resets the results, populates dataset cards and filters with Algloia and SciCrunch data.
244
+ openAlgoliaSearch: function (filter, search = '') {
245
+ this.searchInput = search
246
+ //Proceed normally if cascader is ready
247
+ if (this.cascaderIsReady) {
248
+ this.filter =
249
+ this.$refs.filtersRef.getHierarchicalValidatedFilters(filter)
250
+ //Facets provided but cannot find at least one valid
251
+ //facet. Tell the users the search is invalid and reset
252
+ //facets check boxes.
253
+ if (
254
+ filter &&
255
+ filter.length > 0 &&
256
+ this.filter &&
257
+ this.filter.length === 0
258
+ ) {
259
+ this.$refs.filtersRef.checkShowAllBoxes()
260
+ this.resetSearch()
261
+ } else if (this.filter) {
262
+ this.searchAlgolia(this.filter, search)
263
+ this.$refs.filtersRef.setCascader(this.filter)
264
+ }
265
+ } else {
266
+ //cascader is not ready, perform search if no filter is set,
267
+ //otherwise waith for cascader to be ready
268
+ this.filter = filter
269
+ if (!filter || filter.length == 0) {
270
+ this.searchAlgolia(this.filter, search)
271
+ }
272
+ }
273
+ },
274
+ addFilter: function (filter) {
275
+ if (this.cascaderIsReady) {
276
+ this.resetSearch()
277
+ if (filter) {
278
+ if (this.$refs.filtersRef.addFilter(filter))
279
+ this.$refs.filtersRef.initiateSearch()
280
+ }
281
+ } else {
282
+ if (Array.isArray(this.filter)) {
283
+ this.filter.push(filter)
284
+ } else {
285
+ this.filter = [filter]
286
+ }
287
+ }
288
+ },
289
+ cascaderReady: function () {
290
+ this.cascaderIsReady = true
291
+ this.openSearch(this.filter, this.searchInput)
292
+ },
293
+ clearSearchClicked: function () {
294
+ this.searchInput = ''
295
+ this.resetSearch()
296
+ this.openSearch(this.filter, this.searchInput)
297
+ this.$refs.searchHistory.selectValue = 'Full search history'
298
+ },
299
+ searchEvent: function (event = false) {
300
+ if (event.keyCode === 13 || event instanceof MouseEvent) {
301
+ this.openSearch(this.filter, this.searchInput)
302
+ this.$refs.searchHistory.selectValue = 'Full search history'
303
+ this.$refs.searchHistory.addSearchToHistory(
304
+ this.filter,
305
+ this.searchInput
306
+ )
307
+ }
308
+ },
309
+ updatePMROnlyFlag: function (filters) {
310
+ const pmrSearchObject = filters.find((tmp) => tmp.facet === 'PMR');
311
+ if (pmrSearchObject) {
312
+ this.pmrResultsOnlyFlag = true
313
+ } else {
314
+ this.pmrResultsOnlyFlag = false
315
+ }
316
+ },
317
+ filterUpdate: function (filters) {
318
+ this.filter = [...filters]
319
+
320
+ // Check if PMR is in the filters
321
+ this.updatePMROnlyFlag(filters)
322
+
323
+ // Note that we cannot use the openSearch function as that modifies filters
324
+ this.resetSearch()
325
+ this.searchAlgolia(filters, this.searchInput)
326
+ this.openPMRSearch(filters, this.searchInput)
327
+ this.$emit('search-changed', {
328
+ value: filters,
329
+ type: 'filter-update',
330
+ })
331
+ },
332
+ searchAlgolia(filters, query = '') {
333
+
334
+ // Remove loading if we dont expect any results
335
+ if (this.SPARCLimit() === 0) {
336
+ this.loadingCards = false
337
+ return
338
+ }
339
+
340
+ // Algolia search
341
+
342
+ this.loadingCards = true
343
+ this.algoliaClient
344
+ .anatomyInSearch(getFilters(filters), query)
345
+ .then((r) => {
346
+ // Send result anatomy to the scaffold and flatmap
347
+ EventBus.emit('anatomy-in-datasets', r.forFlatmap)
348
+ EventBus.emit('number-of-datasets-for-anatomies', r.forScaffold)
349
+ })
350
+ this.algoliaClient
351
+ .search(getFilters(filters), query, this.calculateSPARCOffest(), this.SPARCLimit(this.pmrResultsOnlyFlag) )
352
+ .then((searchData) => {
353
+ this.sparcNumberOfHits = searchData.total
354
+ this.discoverIds = searchData.discoverIds
355
+ this._dois = searchData.dois
356
+ searchData.items.forEach((item) => {
357
+ item.detailsReady = false
358
+ this.results.push(item)
359
+ })
360
+ // add the items to the results
361
+ this.results.concat(searchData.items)
362
+ this.loadingCards = false
363
+ this.scrollToTop()
364
+ this.$emit('search-changed', {
365
+ value: this.searchInput,
366
+ type: 'query-update',
367
+ })
368
+ if (this._abortController) this._abortController.abort()
369
+ this._abortController = new AbortController()
370
+ const signal = this._abortController.signal
371
+ //Search ongoing, let the current flow progress
372
+ this.perItemSearch(signal, { count: 0 })
373
+ })
374
+ },
375
+ filtersLoading: function (val) {
376
+ this.loadingCards = val
377
+ },
378
+ numberPerPageUpdate: function (val) {
379
+ this.numberPerPage = val
380
+ this.pageChange(1)
381
+ },
382
+ pageChange: function (page) {
383
+ this.page = page
384
+ this.results = []
385
+ this.calculateVariableRatio()
386
+ this.openSearch(this.filter, this.searchInput, false)
387
+ },
388
+ handleMissingData: function (doi) {
389
+ let i = this.results.findIndex((res) => res.doi === doi)
390
+ if (this.results[i]) this.results[i].detailsReady = true
391
+ },
392
+ perItemSearch: function (signal, data) {
393
+ //Maximum 10 downloads at once to prevent long waiting time
394
+ //between unfinished search and new search
395
+ const maxDownloads = 10
396
+ if (maxDownloads > data.count) {
397
+ const doi = this._dois.shift()
398
+ if (doi) {
399
+ data.count++
400
+ this.callSciCrunch(this.envVars.API_LOCATION, { dois: [doi] }, signal)
401
+ .then((result) => {
402
+ if (result.numberOfHits === 0) this.handleMissingData(doi)
403
+ else this.resultsProcessing(result)
404
+ this.$refs.content.style['overflow-y'] = 'scroll'
405
+ data.count--
406
+ //Async::Download finished, get the next one
407
+ this.perItemSearch(signal, data)
408
+ })
409
+ .catch((result) => {
410
+ if (result.name !== 'AbortError') {
411
+ this.handleMissingData(doi)
412
+ data.count--
413
+ //Async::Download not aborted, get the next one
414
+ this.perItemSearch(signal, data)
415
+ }
416
+ })
417
+ //Check and make another request until it gets to max downloads
418
+ this.perItemSearch(signal, data)
419
+ }
420
+ }
421
+ },
422
+ scrollToTop: function () {
423
+ if (this.$refs.content) {
424
+ this.$refs.content.scroll({ top: 0, behavior: 'smooth' })
425
+ }
426
+ },
427
+ resetPageNavigation: function () {
428
+ this.page = 1
429
+ },
430
+ resultsProcessing: function (data) {
431
+ this.lastSearch = this.searchInput
432
+
433
+ if (data.results.length === 0) {
434
+ return
435
+ }
436
+ data.results.forEach((element) => {
437
+ // match the scicrunch result with algolia result
438
+ let i = this.results.findIndex((res) =>
439
+ element.doi ? element.doi.includes(res.doi) : false
440
+ )
441
+ // Assign scicrunch results to the object
442
+ Object.assign(this.results[i], element)
443
+ // Assign the attributes that need some processing
444
+ Object.assign(this.results[i], {
445
+ numberSamples: element.sampleSize ? parseInt(element.sampleSize) : 0,
446
+ numberSubjects: element.subjectSize
447
+ ? parseInt(element.subjectSize)
448
+ : 0,
449
+ updated:
450
+ (element.updated && element.updated.length) > 0
451
+ ? element.updated[0].timestamp.split('T')[0]
452
+ : '',
453
+ url: element.uri[0],
454
+ datasetId: element.dataset_identifier,
455
+ datasetRevision: element.dataset_revision,
456
+ datasetVersion: element.dataset_version,
457
+ organs:
458
+ element.organs && element.organs.length > 0
459
+ ? [...new Set(element.organs.map((v) => v.name))]
460
+ : undefined,
461
+ species: element.organisms
462
+ ? element.organisms[0].species
463
+ ? [
464
+ ...new Set(
465
+ element.organisms.map((v) =>
466
+ v.species ? v.species.name : null
467
+ )
468
+ ),
469
+ ]
470
+ : undefined
471
+ : undefined, // This processing only includes each gender once into 'sexes'
472
+ scaffolds: element['abi-scaffold-metadata-file'],
473
+ thumbnails: element['abi-thumbnail']
474
+ ? element['abi-thumbnail']
475
+ : element['abi-scaffold-thumbnail'],
476
+ scaffoldViews: element['abi-scaffold-view-file'],
477
+ videos: element.video,
478
+ plots: element['abi-plot'],
479
+ images: element['common-images'],
480
+ contextualInformation:
481
+ element['abi-contextual-information'].length > 0
482
+ ? element['abi-contextual-information']
483
+ : undefined,
484
+ segmentation: element['mbf-segmentation'],
485
+ simulation: element['abi-simulation-file'],
486
+ additionalLinks: element.additionalLinks,
487
+ detailsReady: true,
488
+ })
489
+ this.results[i] = this.results[i]
490
+ })
491
+ },
492
+ createfilterParams: function (params) {
493
+ let p = new URLSearchParams()
494
+ //Check if field is array or value
495
+ for (const key in params) {
496
+ if (Array.isArray(params[key])) {
497
+ params[key].forEach((e) => {
498
+ p.append(key, e)
499
+ })
500
+ } else {
501
+ p.append(key, params[key])
502
+ }
503
+ }
504
+ return p.toString()
505
+ },
506
+ callSciCrunch: function (apiLocation, params = {}, signal) {
507
+ return new Promise((resolve, reject) => {
508
+ // Add parameters if we are sent them
509
+ let fullEndpoint =
510
+ this.envVars.API_LOCATION +
511
+ this.searchEndpoint +
512
+ '?' +
513
+ this.createfilterParams(params)
514
+ fetch(fullEndpoint, { signal })
515
+ .then(handleErrors)
516
+ .then((response) => response.json())
517
+ .then((data) => resolve(data))
518
+ .catch((data) => reject(data))
519
+ })
520
+ },
521
+ getAlgoliaFacets: async function () {
522
+ let facets = await this.algoliaClient.getAlgoliaFacets(
523
+ facetPropPathMapping
524
+ )
525
+ return facets
526
+ },
527
+ searchHistorySearch: function (item) {
528
+ this.searchInput = item.search
529
+ this.filters = item.filters
530
+ this.openSearch(item.filters, item.search)
531
+ },
532
+ },
533
+ mounted: function () {
534
+ // initialise algolia
535
+ this.algoliaClient = new AlgoliaClient(
536
+ this.envVars.ALGOLIA_ID,
537
+ this.envVars.ALGOLIA_KEY,
538
+ this.envVars.PENNSIEVE_API_LOCATION
539
+ )
540
+ this.algoliaClient.initIndex(this.envVars.ALGOLIA_INDEX)
541
+
542
+ // initialise flatmap queries
543
+ this.flatmapQueries = new FlatmapQueries()
544
+ this.flatmapQueries.initialise(this.envVars.FLATMAP_API_LOCATION)
545
+
546
+ // open search
547
+ this.openSearch(this.filter, this.searchInput, this.mode )
548
+ },
549
+ created: function () {
550
+ //Create non-reactive local variables
551
+ this.searchEndpoint = 'dataset_info/using_multiple_dois/'
552
+ },
553
+ }
554
+ </script>
555
+
556
+ <style lang="scss" scoped>
557
+ .dataset-card {
558
+ position: relative;
559
+
560
+ &::before {
561
+ content: "";
562
+ display: block;
563
+ width: calc(100% - 15px);
564
+ height: 100%;
565
+ position: absolute;
566
+ top: 7px;
567
+ left: 7px;
568
+ border-style: solid;
569
+ border-radius: 5px;
570
+ border-color: transparent;
571
+ }
572
+
573
+ &:hover {
574
+ &::before {
575
+ border-color: var(--el-color-primary);
576
+ }
577
+ }
578
+ }
579
+
580
+ .content-card {
581
+ height: 100%;
582
+ flex-flow: column;
583
+ display: flex;
584
+ }
585
+
586
+ .data-type-select {
587
+ width: 90px;
588
+ margin-left: 10px;
589
+ }
590
+
591
+ .button {
592
+ background-color: $app-primary-color;
593
+ border: $app-primary-color;
594
+ color: white;
595
+ }
596
+
597
+ .step-item {
598
+ font-size: 14px;
599
+ margin-bottom: 18px;
600
+ text-align: left;
601
+ }
602
+
603
+ .search-input {
604
+ width: 298px !important;
605
+ height: 40px;
606
+ padding-right: 14px;
607
+ align-items: left;
608
+ }
609
+
610
+ .header {
611
+ border: solid 1px #292b66;
612
+ background-color: #292b66;
613
+ text-align: left;
614
+
615
+ .el-button {
616
+ &:hover,
617
+ &:focus {
618
+ background: $app-primary-color;
619
+ box-shadow: -3px 2px 4px #00000040;
620
+ color: #fff;
621
+ }
622
+ }
623
+ }
624
+
625
+ .pagination {
626
+ padding-bottom: 16px;
627
+ background-color: white;
628
+ padding-left: 95px;
629
+ font-weight: bold;
630
+ }
631
+
632
+ .pagination :deep(button) {
633
+ background-color: white !important;
634
+ }
635
+ .pagination :deep(li) {
636
+ background-color: white !important;
637
+ }
638
+ .pagination :deep(li.is-active) {
639
+ color: $app-primary-color;
640
+ }
641
+
642
+ .error-feedback {
643
+ font-family: Asap;
644
+ font-size: 14px;
645
+ font-style: italic;
646
+ padding-top: 15px;
647
+ }
648
+
649
+ .content-card :deep(.el-card__header) {
650
+ background-color: #292b66;
651
+ padding: 1rem;
652
+ }
653
+
654
+ .content-card :deep(.el-card__body) {
655
+ background-color: #f7faff;
656
+ overflow-y: hidden;
657
+ padding: 1rem;
658
+ }
659
+
660
+ .content {
661
+ // width: 515px;
662
+ flex: 1 1 auto;
663
+ box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.06);
664
+ border: solid 1px #e4e7ed;
665
+ background-color: #ffffff;
666
+ overflow-y: scroll;
667
+ scrollbar-width: thin;
668
+ }
669
+
670
+ .content :deep(.el-loading-spinner .path) {
671
+ stroke: $app-primary-color;
672
+ }
673
+
674
+ .content :deep(.step-item:first-child .seperator-path) {
675
+ display: none;
676
+ }
677
+
678
+ .content :deep(.step-item:not(:first-child) .seperator-path) {
679
+ width: 455px;
680
+ height: 0px;
681
+ border: solid 1px #e4e7ed;
682
+ background-color: #e4e7ed;
683
+ }
684
+
685
+ .scrollbar::-webkit-scrollbar-track {
686
+ border-radius: 10px;
687
+ background-color: #f5f5f5;
688
+ }
689
+
690
+ .scrollbar::-webkit-scrollbar {
691
+ width: 12px;
692
+ right: -12px;
693
+ background-color: #f5f5f5;
694
+ }
695
+
696
+ .scrollbar::-webkit-scrollbar-thumb {
697
+ border-radius: 4px;
698
+ box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.06);
699
+ background-color: #979797;
700
+ }
701
+
702
+ :deep(.el-input__suffix) {
703
+ padding-right: 0px;
704
+ }
705
+
706
+ :deep(.my-drawer) {
707
+ background: rgba(0, 0, 0, 0);
708
+ box-shadow: none;
709
+ }
710
+ </style>