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