@abi-software/map-side-bar 1.3.32 → 1.3.34

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,609 +1,609 @@
1
- <template>
2
- <div class="filters">
3
- <map-svg-sprite-color />
4
- <transition name="el-zoom-in-top">
5
- <span v-show="showFilters" class="search-filters transition-box">
6
- <custom-cascader
7
- class="cascader"
8
- ref="cascader"
9
- v-model="cascadeSelected"
10
- placeholder
11
- :collapse-tags="true"
12
- :options="options"
13
- :props="props"
14
- @change="cascadeEvent($event)"
15
- @expand-change="cascadeExpandChange"
16
- :show-all-levels="false"
17
- :append-to-body="false"
18
- @tags-changed="tagsChangedCallback"
19
- ></custom-cascader>
20
- <div v-if="showFiltersText" class="filter-default-value">
21
- Filters
22
- </div>
23
- <el-popover
24
- title="How do filters work?"
25
- width="250"
26
- trigger="hover"
27
- :append-to-body=false
28
- popper-class="popover"
29
- >
30
- <map-svg-icon slot="reference" icon="help" class="help"/>
31
- <div >
32
- <strong>Within categories:</strong> OR
33
- <br/>
34
- example: 'heart' OR 'colon'
35
- <br/>
36
- <br/>
37
- <strong>Between categories:</strong> AND
38
- <br/>
39
- example: 'rat' AND 'lung'
40
- </div>
41
- </el-popover>
42
-
43
- </span>
44
- </transition>
45
-
46
- <el-select
47
- class="number-shown-select"
48
- v-model="numberShown"
49
- placeholder="10"
50
- @change="numberShownChanged($event)"
51
- >
52
- <el-option
53
- v-for="item in numberDatasetsShown"
54
- :key="item"
55
- :label="item"
56
- :value="item"
57
- ></el-option>
58
- </el-select>
59
- <span class="dataset-results-feedback">{{ this.numberOfResultsText }}</span>
60
- </div>
61
- </template>
62
-
63
-
64
- <script>
65
- /* eslint-disable no-alert, no-console */
66
- import Vue from "vue";
67
- import { Option, Select, Popover } from "element-ui";
68
- import CustomCascader from "./Cascader";
69
- import lang from "element-ui/lib/locale/lang/en";
70
- import locale from "element-ui/lib/locale";
71
- import speciesMap from "./species-map";
72
- import { MapSvgIcon, MapSvgSpriteColor } from "@abi-software/svg-sprite";
73
-
74
- import {AlgoliaClient} from "../algolia/algolia.js";
75
- import {facetPropPathMapping} from "../algolia/utils.js";
76
-
77
- locale.use(lang);
78
- Vue.use(Option);
79
- Vue.use(Select);
80
- Vue.use(Popover)
81
-
82
- const capitalise = function (txt) {
83
- return txt.charAt(0).toUpperCase() + txt.slice(1);
84
- };
85
-
86
- const convertReadableLabel = function (original) {
87
- const name = original.toLowerCase();
88
- if (speciesMap[name]) {
89
- return capitalise(speciesMap[name]);
90
- } else {
91
- return capitalise(name);
92
- }
93
- };
94
-
95
- export default {
96
- name: "SearchFilters",
97
- components: {
98
- CustomCascader,
99
- MapSvgIcon,
100
- MapSvgSpriteColor,
101
- },
102
- props: {
103
- /**
104
- * Object containing information for
105
- * the required viewing.
106
- */
107
- entry: Object,
108
- envVars: {
109
- type: Object,
110
- default: ()=>{}
111
- },
112
- },
113
- data: function () {
114
- return {
115
- cascaderIsReady: false,
116
- previousShowAllChecked: {
117
- species: false,
118
- gender: false,
119
- organ: false,
120
- datasets: false,
121
- },
122
- showFilters: true,
123
- showFiltersText: true,
124
- cascadeSelected: [],
125
- cascadeSelectedWithBoolean: [],
126
- numberShown: 10,
127
- filters: [],
128
- facets: ["Species", "Gender", "Organ", "Datasets"],
129
- numberDatasetsShown: ["10", "20", "50"],
130
- props: { multiple: true },
131
- options: [
132
- {
133
- value: "Species",
134
- label: "Species",
135
- children: [{}],
136
- },
137
- ],
138
- };
139
- },
140
- computed: {
141
- numberOfResultsText: function () {
142
- return `${this.entry.numberOfHits} results | Showing`;
143
- },
144
- },
145
- methods: {
146
- createCascaderItemValue: function (term, facet) {
147
- if (facet) return term + ">" + facet;
148
- else return term;
149
- },
150
- populateCascader: function () {
151
- return new Promise((resolve) => {
152
- // Algolia facet serach
153
- this.algoliaClient.getAlgoliaFacets(facetPropPathMapping)
154
- .then((data) => {
155
- this.facets = data;
156
- this.options = data;
157
-
158
- // create top level of options in cascader
159
- this.options.forEach((facet, i) => {
160
- this.options[i].label = convertReadableLabel(facet.label);
161
- this.options[i].value = this.createCascaderItemValue(
162
- facet.key,
163
- undefined
164
- );
165
-
166
- // put "Show all" as first option
167
- this.options[i].children.unshift({
168
- value: this.createCascaderItemValue("Show all"),
169
- label: "Show all",
170
- });
171
-
172
- // populate second level of options
173
- this.options[i].children.forEach((facetItem, j) => {
174
- this.options[i].children[j].label = convertReadableLabel(
175
- facetItem.label
176
- );
177
- this.options[i].children[j].value =
178
- this.createCascaderItemValue(facet.label, facetItem.label);
179
- });
180
- });
181
- this.createDataTypeFacet();
182
- })
183
- .finally(() => {
184
- resolve();
185
- });
186
- });
187
- },
188
- tagsChangedCallback: function (presentTags) {
189
- if (presentTags.length > 0) {
190
- this.showFiltersText = false;
191
- } else {
192
- this.showFiltersText = true;
193
- }
194
- },
195
- // cascadeEvent: initiate searches based off cascader changes
196
- cascadeEvent: function (event) {
197
- if (event) {
198
- // Check for show all in selected cascade options
199
- event = this.showAllEventModifier(event);
200
-
201
- // Create results for the filter update
202
- let filterKeys = event.filter( selection => selection !== undefined).map( fs => ({
203
- facetPropPath: fs[0],
204
- facet: fs[1].split(">")[1],
205
- term: fs[1].split(">")[0],
206
- AND: fs[2] // for setting the boolean
207
- }))
208
-
209
- // Move results from arrays to object for use on scicrunch (note that we remove 'duplicate' as that is only needed for filter keys)
210
- let filters = event.filter( selection => selection !== undefined).map( fs => {
211
- let propPath = fs[0].includes('duplicate') ? fs[0].split('duplicate')[0] : fs[0]
212
- return {
213
- facetPropPath: propPath,
214
- facet: fs[1].split(">")[1],
215
- term: fs[1].split(">")[0],
216
- AND: fs[2] // for setting the boolean
217
- }
218
- })
219
-
220
-
221
- this.$emit('loading', true) // let sidebarcontent wait for the requests
222
-
223
- this.$emit("filterResults", filters); // emit filters for apps above sidebar
224
- this.setCascader(filterKeys); //update our cascader v-model if we modified the event
225
- this.makeCascadeLabelsClickable();
226
- }
227
- },
228
- // showAllEventModifier: Modifies a cascade event to unclick all selections in category if "show all" is clicked. Also unchecks "Show all" if any secection is clicked
229
- // *NOTE* Does NOT remove 'Show all' selections from showing in 'cascadeSelected'
230
- showAllEventModifier: function (event) {
231
- // check if show all is in the cascader checked option list
232
- let hasShowAll = event
233
- .map((ev) => (ev ? ev[1].toLowerCase().includes("show all") : false))
234
- .includes(true);
235
- // remove all selected options below the show all if checked
236
- if (hasShowAll) {
237
- let modifiedEvent = [];
238
- let facetMaps = {};
239
- //catagorised different facet items
240
- for (const i in event) {
241
- if (facetMaps[event[i][0]] === undefined) facetMaps[event[i][0]] = [];
242
- facetMaps[event[i][0]].push(event[i]);
243
- }
244
- // go through each facets
245
- for (const facet in facetMaps) {
246
- let showAll = undefined;
247
- // Find the show all item if any
248
- for (let i = facetMaps[facet].length - 1; i >= 0; i--) {
249
- if (facetMaps[facet][i][1].toLowerCase().includes("show all")) {
250
- //seperate the showAll item and the rest
251
- showAll = facetMaps[facet].splice(i, 1)[0];
252
- break;
253
- }
254
- }
255
- if (showAll) {
256
- if (this.previousShowAllChecked[facet]) {
257
- //Unset the show all if it was present previously
258
- //and there are other items
259
- if (facetMaps[facet].length > 0)
260
- modifiedEvent.push(...facetMaps[facet]);
261
- else modifiedEvent.push(showAll);
262
- } else {
263
- //showAll is turned on
264
- modifiedEvent.push(showAll);
265
- }
266
- } else {
267
- modifiedEvent.push(...facetMaps[facet]);
268
- }
269
- }
270
- //Make sure the expanded item are sorted first.
271
- return modifiedEvent.sort((a, b) => {
272
- if (this.__expandItem__) {
273
- if (a[0] == this.__expandItem__) {
274
- if (b[0] == this.__expandItem__) {
275
- return 0;
276
- } else {
277
- return -1;
278
- }
279
- } else if (b[0] == this.__expandItem__) {
280
- if (a[0] == this.__expandItem__) {
281
- return 0;
282
- } else {
283
- return 1;
284
- }
285
- } else {
286
- return 0;
287
- }
288
- } else return 0;
289
- });
290
- }
291
- return event;
292
- },
293
- createDataTypeFacet: function(){
294
- let dataFacet = {...this.facets[2]} // copy the 'Experiemental approach' facet
295
- let count = this.facets.at(-1).id // get the last id count
296
-
297
- // Step through the children that are valid data types, switch thier values
298
- let newChildren = dataFacet.children.filter( el=> {
299
- if (el.label === 'Scaffold' || el.label === 'Simulation' || el.label === 'Show all'){
300
- el.key = el.label
301
- el.id = count
302
- el.value = el.value.replace('Experimental approach', 'Data type')
303
- count++
304
- return el
305
- }
306
- })
307
- dataFacet.id = count
308
- dataFacet.key = 'Data type'
309
- // Add 'duplicate' so that the key is unique. This is removed in the cascade event for filtering
310
- dataFacet.value += 'duplicate'
311
- dataFacet.children = newChildren
312
- dataFacet.label = 'Data type'
313
- this.facets.push(dataFacet)
314
- },
315
- cascadeExpandChange: function (event) {
316
- //work around as the expand item may change on modifying the cascade props
317
- this.__expandItem__ = event;
318
- this.makeCascadeLabelsClickable();
319
- },
320
- numberShownChanged: function (event) {
321
- this.$emit("numberPerPage", parseInt(event));
322
- },
323
- updatePreviousShowAllChecked: function (options) {
324
- //Reset the states
325
- for (const facet in this.previousShowAllChecked) {
326
- this.previousShowAllChecked[facet] = false;
327
- }
328
- options.forEach((element) => {
329
- if (element[1].toLowerCase().includes("show all"))
330
- this.previousShowAllChecked[element[0]] = true;
331
- });
332
- },
333
- // setCascader: Clears previous selections and takes in an array of facets to select: filterFacets
334
- // facets are in the form:
335
- // {
336
- // facetPropPath: 'anatomy.organ.name',
337
- // term: 'Sex',
338
- // facet: 'Male'
339
- // AND: true // Optional value for setting the boolean within a facet
340
- // }
341
- setCascader: function (filterFacets) {
342
- //Do not set the value unless it is ready
343
- if (this.cascaderIsReady && filterFacets && filterFacets.length != 0) {
344
- this.cascadeSelected = filterFacets.map(e => {
345
- return [
346
- e.facetPropPath,
347
- this.createCascaderItemValue(capitalise(e.term), e.facet),
348
- ]
349
- });
350
-
351
- // Unforttunately the cascader is very particular about it's v-model
352
- // to get around this we create a clone of it and use this clone for adding our boolean information
353
- this.cascadeSelectedWithBoolean= filterFacets.map(e => {
354
- return [
355
- e.facetPropPath,
356
- this.createCascaderItemValue(capitalise(e.term), e.facet),
357
- e.AND
358
- ]
359
- });
360
- this.updatePreviousShowAllChecked(this.cascadeSelected);
361
- }
362
- },
363
- addFilter: function (filter) {
364
- //Do not set the value unless it is ready
365
- if (this.cascaderIsReady && filter) {
366
- if (this.validateFilter(filter)) {
367
- this.cascadeSelected.filter(f=>f.term != filter.term)
368
- this.cascadeSelected.push([filter.facetPropPath, this.createCascaderItemValue(filter.term, filter.facet), filter.AND])
369
- this.cascadeSelectedWithBoolean.push([filter.facetPropPath, this.createCascaderItemValue(filter.term, filter.facet), filter.AND])
370
- // The 'AND' her is to set the boolean value when we search on the filters. It can be undefined without breaking anything
371
- return true;
372
- }
373
- }
374
- },
375
- initiateSearch: function() {
376
- this.cascadeEvent(this.cascadeSelectedWithBoolean)
377
- },
378
- // checkShowAllBoxes: Checks each 'Show all' cascade option by using the setCascader function
379
- checkShowAllBoxes: function(){
380
- this.setCascader(
381
- this.options.map(option => {
382
- return {
383
- facetPropPath: option.value,
384
- term: option.label,
385
- facet: 'Show all'
386
- }
387
- })
388
- )
389
- },
390
- makeCascadeLabelsClickable: function () {
391
- // Next tick allows the cascader menu to change
392
- this.$nextTick(() => {
393
- this.$refs.cascader.$el
394
- .querySelectorAll(".el-cascader-node__label")
395
- .forEach((el) => {
396
- // step through each cascade label
397
- el.onclick = function () {
398
- const checkbox = this.previousElementSibling;
399
- if (checkbox) {
400
- if (!checkbox.parentElement.attributes["aria-owns"]) {
401
- // check if we are at the lowest level of cascader
402
- this.previousElementSibling.click(); // Click the checkbox
403
- }
404
- }
405
- };
406
- });
407
- });
408
- },
409
- /**
410
- * Validate ther filter term to make sure the term is correct
411
- */
412
- validateFilter: function(filter) {
413
- if (filter && filter.facet && filter.term) {
414
- const item = this.createCascaderItemValue(filter.term, filter.facet);
415
- const facet = this.options.find(element => element.value === filter.facetPropPath);
416
- if (facet) {
417
- const filter = facet.children.find(element => element.value === item);
418
- if (filter)
419
- return true;
420
- }
421
- }
422
- return false;
423
- },
424
- /**
425
- * Return a list of valid filers given a list of filters,
426
- */
427
- getValidatedFilters: function (filters) {
428
- if (filters) {
429
- if (this.cascaderIsReady) {
430
- const result = [];
431
- filters.forEach(filter => {
432
- if (this.validateFilter(filter)) {
433
- result.push(filter);
434
- }
435
- });
436
- return result;
437
- } else return filters;
438
- }
439
- return [];
440
- },
441
- },
442
- mounted: function () {
443
- this.algoliaClient = new AlgoliaClient(this.envVars.ALGOLIA_ID, this.envVars.ALGOLIA_KEY, this.envVars.PENNSIEVE_API_LOCATION);
444
- this.algoliaClient.initIndex(this.envVars.ALGOLIA_INDEX);
445
- this.populateCascader().then(() => {
446
- this.cascaderIsReady = true;
447
- this.checkShowAllBoxes();
448
- this.setCascader(this.entry.filterFacets);
449
- this.makeCascadeLabelsClickable();
450
- this.$emit("cascaderReady");
451
- });
452
- },
453
- };
454
- </script>
455
-
456
- <!-- Add "scoped" attribute to limit CSS to this component only -->
457
- <style scoped>
458
- .filter-default-value {
459
- pointer-events: none;
460
- position: absolute;
461
- top: 0;
462
- left: 0;
463
- padding-top: 10px;
464
- padding-left: 16px;
465
- }
466
-
467
- .help {
468
- width: 24px !important;
469
- height: 24px;
470
- transform: scale(1.1);
471
- color: #8300bf;
472
- cursor: pointer;
473
- }
474
-
475
- .popover {
476
- color: rgb(48, 49, 51);
477
- font-family: Asap;
478
- margin: 12px;
479
- }
480
-
481
- .filter-icon-inside {
482
- width: 12px !important;
483
- height: 12px !important;
484
- color: #292b66;
485
- transform: scale(2) !important;
486
- margin-bottom: 0px !important;
487
- }
488
-
489
- .cascader {
490
- font-family: Asap;
491
- font-size: 14px;
492
- font-weight: 500;
493
- font-stretch: normal;
494
- font-style: normal;
495
- line-height: normal;
496
- letter-spacing: normal;
497
- color: #292b66;
498
- text-align: center;
499
- padding-bottom: 6px;
500
- }
501
-
502
- .cascader >>> .el-cascder-panel {
503
- max-height: 500px;
504
- }
505
-
506
- .cascader >>> .el-scrollbar__wrap {
507
- overflow-x: hidden;
508
- margin-bottom: 2px !important;
509
- }
510
-
511
- .cascader >>> li[aria-owns*="cascader"] > .el-checkbox {
512
- display: none;
513
- }
514
-
515
- .dataset-results-feedback {
516
- float: right;
517
- text-align: right;
518
- color: rgb(48, 49, 51);
519
- font-family: Asap;
520
- font-size: 18px;
521
- font-weight: 500;
522
- padding-top: 8px;
523
- }
524
-
525
- .search-filters {
526
- position: relative;
527
- float: left;
528
- padding-right: 15px;
529
- padding-bottom: 12px;
530
- }
531
-
532
- .number-shown-select {
533
- float: right;
534
- }
535
-
536
- .number-shown-select >>> .el-input__inner {
537
- width: 68px;
538
- height: 40px;
539
- color: rgb(48, 49, 51);
540
- }
541
-
542
- .search-filters >>> .el-cascader-node.is-active {
543
- color: #8300bf;
544
- }
545
-
546
- .search-filters >>> .el-cascader-node.in-active-path {
547
- color: #8300bf;
548
- }
549
-
550
- .search-filters >>> .el-checkbox__input.is-checked > .el-checkbox__inner {
551
- background-color: #8300bf;
552
- border-color: #8300bf;
553
- }
554
-
555
- .cascader >>> .el-cascader-menu:nth-child(2) .el-cascader-node:first-child {
556
- border-bottom: 1px solid #e4e7ed;
557
- }
558
-
559
- .cascader >>> .el-cascader-node__label {
560
- text-align: left;
561
- }
562
-
563
- .filters >>> .el-popover {
564
- background: #f3ecf6 !important;
565
- border: 1px solid #8300BF;
566
- border-radius: 4px;
567
- color: #303133 !important;
568
- font-size: 12px;
569
- line-height: 18px;
570
-
571
-
572
- }
573
-
574
- .filters >>> .el-popover[x-placement^="top"] .popper__arrow {
575
- border-top-color: #8300BF;
576
- border-bottom-width: 0;
577
- }
578
- .filters >>> .el-popover[x-placement^="top"] .popper__arrow::after {
579
- border-top-color: #f3ecf6;
580
- border-bottom-width: 0;
581
- }
582
-
583
- .filters >>> .el-popover[x-placement^="bottom"] .popper__arrow {
584
- border-top-width: 0;
585
- border-bottom-color: #8300BF;
586
- }
587
- .filters >>> .el-popover[x-placement^="bottom"] .popper__arrow::after {
588
- border-top-width: 0;
589
- border-bottom-color: #f3ecf6;
590
- }
591
-
592
- .filters >>> .el-popover[x-placement^="right"] .popper__arrow {
593
- border-right-color: #8300BF;
594
- border-left-width: 0;
595
- }
596
- .filters >>> .el-popover[x-placement^="right"] .popper__arrow::after {
597
- border-right-color: #f3ecf6;
598
- border-left-width: 0;
599
- }
600
-
601
- .filters >>> .el-popover[x-placement^="left"] .popper__arrow {
602
- border-right-width: 0;
603
- border-left-color: #8300BF;
604
- }
605
- .filters >>> .el-popover[x-placement^="left"] .popper__arrow::after {
606
- border-right-width: 0;
607
- border-left-color: #f3ecf6;
608
- }
609
- </style>
1
+ <template>
2
+ <div class="filters">
3
+ <map-svg-sprite-color />
4
+ <transition name="el-zoom-in-top">
5
+ <span v-show="showFilters" class="search-filters transition-box">
6
+ <custom-cascader
7
+ class="cascader"
8
+ ref="cascader"
9
+ v-model="cascadeSelected"
10
+ placeholder
11
+ :collapse-tags="true"
12
+ :options="options"
13
+ :props="props"
14
+ @change="cascadeEvent($event)"
15
+ @expand-change="cascadeExpandChange"
16
+ :show-all-levels="false"
17
+ :append-to-body="false"
18
+ @tags-changed="tagsChangedCallback"
19
+ ></custom-cascader>
20
+ <div v-if="showFiltersText" class="filter-default-value">
21
+ Filters
22
+ </div>
23
+ <el-popover
24
+ title="How do filters work?"
25
+ width="250"
26
+ trigger="hover"
27
+ :append-to-body=false
28
+ popper-class="popover"
29
+ >
30
+ <map-svg-icon slot="reference" icon="help" class="help"/>
31
+ <div >
32
+ <strong>Within categories:</strong> OR
33
+ <br/>
34
+ example: 'heart' OR 'colon'
35
+ <br/>
36
+ <br/>
37
+ <strong>Between categories:</strong> AND
38
+ <br/>
39
+ example: 'rat' AND 'lung'
40
+ </div>
41
+ </el-popover>
42
+
43
+ </span>
44
+ </transition>
45
+
46
+ <el-select
47
+ class="number-shown-select"
48
+ v-model="numberShown"
49
+ placeholder="10"
50
+ @change="numberShownChanged($event)"
51
+ >
52
+ <el-option
53
+ v-for="item in numberDatasetsShown"
54
+ :key="item"
55
+ :label="item"
56
+ :value="item"
57
+ ></el-option>
58
+ </el-select>
59
+ <span class="dataset-results-feedback">{{ this.numberOfResultsText }}</span>
60
+ </div>
61
+ </template>
62
+
63
+
64
+ <script>
65
+ /* eslint-disable no-alert, no-console */
66
+ import Vue from "vue";
67
+ import { Option, Select, Popover } from "element-ui";
68
+ import CustomCascader from "./Cascader";
69
+ import lang from "element-ui/lib/locale/lang/en";
70
+ import locale from "element-ui/lib/locale";
71
+ import speciesMap from "./species-map";
72
+ import { MapSvgIcon, MapSvgSpriteColor } from "@abi-software/svg-sprite";
73
+
74
+ import {AlgoliaClient} from "../algolia/algolia.js";
75
+ import {facetPropPathMapping} from "../algolia/utils.js";
76
+
77
+ locale.use(lang);
78
+ Vue.use(Option);
79
+ Vue.use(Select);
80
+ Vue.use(Popover)
81
+
82
+ const capitalise = function (txt) {
83
+ return txt.charAt(0).toUpperCase() + txt.slice(1);
84
+ };
85
+
86
+ const convertReadableLabel = function (original) {
87
+ const name = original.toLowerCase();
88
+ if (speciesMap[name]) {
89
+ return capitalise(speciesMap[name]);
90
+ } else {
91
+ return capitalise(name);
92
+ }
93
+ };
94
+
95
+ export default {
96
+ name: "SearchFilters",
97
+ components: {
98
+ CustomCascader,
99
+ MapSvgIcon,
100
+ MapSvgSpriteColor,
101
+ },
102
+ props: {
103
+ /**
104
+ * Object containing information for
105
+ * the required viewing.
106
+ */
107
+ entry: Object,
108
+ envVars: {
109
+ type: Object,
110
+ default: ()=>{}
111
+ },
112
+ },
113
+ data: function () {
114
+ return {
115
+ cascaderIsReady: false,
116
+ previousShowAllChecked: {
117
+ species: false,
118
+ gender: false,
119
+ organ: false,
120
+ datasets: false,
121
+ },
122
+ showFilters: true,
123
+ showFiltersText: true,
124
+ cascadeSelected: [],
125
+ cascadeSelectedWithBoolean: [],
126
+ numberShown: 10,
127
+ filters: [],
128
+ facets: ["Species", "Gender", "Organ", "Datasets"],
129
+ numberDatasetsShown: ["10", "20", "50"],
130
+ props: { multiple: true },
131
+ options: [
132
+ {
133
+ value: "Species",
134
+ label: "Species",
135
+ children: [{}],
136
+ },
137
+ ],
138
+ };
139
+ },
140
+ computed: {
141
+ numberOfResultsText: function () {
142
+ return `${this.entry.numberOfHits} results | Showing`;
143
+ },
144
+ },
145
+ methods: {
146
+ createCascaderItemValue: function (term, facet) {
147
+ if (facet) return term + ">" + facet;
148
+ else return term;
149
+ },
150
+ populateCascader: function () {
151
+ return new Promise((resolve) => {
152
+ // Algolia facet serach
153
+ this.algoliaClient.getAlgoliaFacets(facetPropPathMapping)
154
+ .then((data) => {
155
+ this.facets = data;
156
+ this.options = data;
157
+
158
+ // create top level of options in cascader
159
+ this.options.forEach((facet, i) => {
160
+ this.options[i].label = convertReadableLabel(facet.label);
161
+ this.options[i].value = this.createCascaderItemValue(
162
+ facet.key,
163
+ undefined
164
+ );
165
+
166
+ // put "Show all" as first option
167
+ this.options[i].children.unshift({
168
+ value: this.createCascaderItemValue("Show all"),
169
+ label: "Show all",
170
+ });
171
+
172
+ // populate second level of options
173
+ this.options[i].children.forEach((facetItem, j) => {
174
+ this.options[i].children[j].label = convertReadableLabel(
175
+ facetItem.label
176
+ );
177
+ this.options[i].children[j].value =
178
+ this.createCascaderItemValue(facet.label, facetItem.label);
179
+ });
180
+ });
181
+ this.createDataTypeFacet();
182
+ })
183
+ .finally(() => {
184
+ resolve();
185
+ });
186
+ });
187
+ },
188
+ tagsChangedCallback: function (presentTags) {
189
+ if (presentTags.length > 0) {
190
+ this.showFiltersText = false;
191
+ } else {
192
+ this.showFiltersText = true;
193
+ }
194
+ },
195
+ // cascadeEvent: initiate searches based off cascader changes
196
+ cascadeEvent: function (event) {
197
+ if (event) {
198
+ // Check for show all in selected cascade options
199
+ event = this.showAllEventModifier(event);
200
+
201
+ // Create results for the filter update
202
+ let filterKeys = event.filter( selection => selection !== undefined).map( fs => ({
203
+ facetPropPath: fs[0],
204
+ facet: fs[1].split(">")[1],
205
+ term: fs[1].split(">")[0],
206
+ AND: fs[2] // for setting the boolean
207
+ }))
208
+
209
+ // Move results from arrays to object for use on scicrunch (note that we remove 'duplicate' as that is only needed for filter keys)
210
+ let filters = event.filter( selection => selection !== undefined).map( fs => {
211
+ let propPath = fs[0].includes('duplicate') ? fs[0].split('duplicate')[0] : fs[0]
212
+ return {
213
+ facetPropPath: propPath,
214
+ facet: fs[1].split(">")[1],
215
+ term: fs[1].split(">")[0],
216
+ AND: fs[2] // for setting the boolean
217
+ }
218
+ })
219
+
220
+
221
+ this.$emit('loading', true) // let sidebarcontent wait for the requests
222
+
223
+ this.$emit("filterResults", filters); // emit filters for apps above sidebar
224
+ this.setCascader(filterKeys); //update our cascader v-model if we modified the event
225
+ this.makeCascadeLabelsClickable();
226
+ }
227
+ },
228
+ // showAllEventModifier: Modifies a cascade event to unclick all selections in category if "show all" is clicked. Also unchecks "Show all" if any secection is clicked
229
+ // *NOTE* Does NOT remove 'Show all' selections from showing in 'cascadeSelected'
230
+ showAllEventModifier: function (event) {
231
+ // check if show all is in the cascader checked option list
232
+ let hasShowAll = event
233
+ .map((ev) => (ev ? ev[1].toLowerCase().includes("show all") : false))
234
+ .includes(true);
235
+ // remove all selected options below the show all if checked
236
+ if (hasShowAll) {
237
+ let modifiedEvent = [];
238
+ let facetMaps = {};
239
+ //catagorised different facet items
240
+ for (const i in event) {
241
+ if (facetMaps[event[i][0]] === undefined) facetMaps[event[i][0]] = [];
242
+ facetMaps[event[i][0]].push(event[i]);
243
+ }
244
+ // go through each facets
245
+ for (const facet in facetMaps) {
246
+ let showAll = undefined;
247
+ // Find the show all item if any
248
+ for (let i = facetMaps[facet].length - 1; i >= 0; i--) {
249
+ if (facetMaps[facet][i][1].toLowerCase().includes("show all")) {
250
+ //seperate the showAll item and the rest
251
+ showAll = facetMaps[facet].splice(i, 1)[0];
252
+ break;
253
+ }
254
+ }
255
+ if (showAll) {
256
+ if (this.previousShowAllChecked[facet]) {
257
+ //Unset the show all if it was present previously
258
+ //and there are other items
259
+ if (facetMaps[facet].length > 0)
260
+ modifiedEvent.push(...facetMaps[facet]);
261
+ else modifiedEvent.push(showAll);
262
+ } else {
263
+ //showAll is turned on
264
+ modifiedEvent.push(showAll);
265
+ }
266
+ } else {
267
+ modifiedEvent.push(...facetMaps[facet]);
268
+ }
269
+ }
270
+ //Make sure the expanded item are sorted first.
271
+ return modifiedEvent.sort((a, b) => {
272
+ if (this.__expandItem__) {
273
+ if (a[0] == this.__expandItem__) {
274
+ if (b[0] == this.__expandItem__) {
275
+ return 0;
276
+ } else {
277
+ return -1;
278
+ }
279
+ } else if (b[0] == this.__expandItem__) {
280
+ if (a[0] == this.__expandItem__) {
281
+ return 0;
282
+ } else {
283
+ return 1;
284
+ }
285
+ } else {
286
+ return 0;
287
+ }
288
+ } else return 0;
289
+ });
290
+ }
291
+ return event;
292
+ },
293
+ createDataTypeFacet: function(){
294
+ let dataFacet = {...this.facets[2]} // copy the 'Experiemental approach' facet
295
+ let count = this.facets.at(-1).id // get the last id count
296
+
297
+ // Step through the children that are valid data types, switch thier values
298
+ let newChildren = dataFacet.children.filter( el=> {
299
+ if (el.label === 'Scaffold' || el.label === 'Simulation' || el.label === 'Show all'){
300
+ el.key = el.label
301
+ el.id = count
302
+ el.value = el.value.replace('Experimental approach', 'Data type')
303
+ count++
304
+ return el
305
+ }
306
+ })
307
+ dataFacet.id = count
308
+ dataFacet.key = 'Data type'
309
+ // Add 'duplicate' so that the key is unique. This is removed in the cascade event for filtering
310
+ dataFacet.value += 'duplicate'
311
+ dataFacet.children = newChildren
312
+ dataFacet.label = 'Data type'
313
+ this.facets.push(dataFacet)
314
+ },
315
+ cascadeExpandChange: function (event) {
316
+ //work around as the expand item may change on modifying the cascade props
317
+ this.__expandItem__ = event;
318
+ this.makeCascadeLabelsClickable();
319
+ },
320
+ numberShownChanged: function (event) {
321
+ this.$emit("numberPerPage", parseInt(event));
322
+ },
323
+ updatePreviousShowAllChecked: function (options) {
324
+ //Reset the states
325
+ for (const facet in this.previousShowAllChecked) {
326
+ this.previousShowAllChecked[facet] = false;
327
+ }
328
+ options.forEach((element) => {
329
+ if (element[1].toLowerCase().includes("show all"))
330
+ this.previousShowAllChecked[element[0]] = true;
331
+ });
332
+ },
333
+ // setCascader: Clears previous selections and takes in an array of facets to select: filterFacets
334
+ // facets are in the form:
335
+ // {
336
+ // facetPropPath: 'anatomy.organ.name',
337
+ // term: 'Sex',
338
+ // facet: 'Male'
339
+ // AND: true // Optional value for setting the boolean within a facet
340
+ // }
341
+ setCascader: function (filterFacets) {
342
+ //Do not set the value unless it is ready
343
+ if (this.cascaderIsReady && filterFacets && filterFacets.length != 0) {
344
+ this.cascadeSelected = filterFacets.map(e => {
345
+ return [
346
+ e.facetPropPath,
347
+ this.createCascaderItemValue(capitalise(e.term), e.facet),
348
+ ]
349
+ });
350
+
351
+ // Unforttunately the cascader is very particular about it's v-model
352
+ // to get around this we create a clone of it and use this clone for adding our boolean information
353
+ this.cascadeSelectedWithBoolean= filterFacets.map(e => {
354
+ return [
355
+ e.facetPropPath,
356
+ this.createCascaderItemValue(capitalise(e.term), e.facet),
357
+ e.AND
358
+ ]
359
+ });
360
+ this.updatePreviousShowAllChecked(this.cascadeSelected);
361
+ }
362
+ },
363
+ addFilter: function (filter) {
364
+ //Do not set the value unless it is ready
365
+ if (this.cascaderIsReady && filter) {
366
+ if (this.validateFilter(filter)) {
367
+ this.cascadeSelected.filter(f=>f.term != filter.term)
368
+ this.cascadeSelected.push([filter.facetPropPath, this.createCascaderItemValue(filter.term, filter.facet), filter.AND])
369
+ this.cascadeSelectedWithBoolean.push([filter.facetPropPath, this.createCascaderItemValue(filter.term, filter.facet), filter.AND])
370
+ // The 'AND' her is to set the boolean value when we search on the filters. It can be undefined without breaking anything
371
+ return true;
372
+ }
373
+ }
374
+ },
375
+ initiateSearch: function() {
376
+ this.cascadeEvent(this.cascadeSelectedWithBoolean)
377
+ },
378
+ // checkShowAllBoxes: Checks each 'Show all' cascade option by using the setCascader function
379
+ checkShowAllBoxes: function(){
380
+ this.setCascader(
381
+ this.options.map(option => {
382
+ return {
383
+ facetPropPath: option.value,
384
+ term: option.label,
385
+ facet: 'Show all'
386
+ }
387
+ })
388
+ )
389
+ },
390
+ makeCascadeLabelsClickable: function () {
391
+ // Next tick allows the cascader menu to change
392
+ this.$nextTick(() => {
393
+ this.$refs.cascader.$el
394
+ .querySelectorAll(".el-cascader-node__label")
395
+ .forEach((el) => {
396
+ // step through each cascade label
397
+ el.onclick = function () {
398
+ const checkbox = this.previousElementSibling;
399
+ if (checkbox) {
400
+ if (!checkbox.parentElement.attributes["aria-owns"]) {
401
+ // check if we are at the lowest level of cascader
402
+ this.previousElementSibling.click(); // Click the checkbox
403
+ }
404
+ }
405
+ };
406
+ });
407
+ });
408
+ },
409
+ /**
410
+ * Validate ther filter term to make sure the term is correct
411
+ */
412
+ validateFilter: function(filter) {
413
+ if (filter && filter.facet && filter.term) {
414
+ const item = this.createCascaderItemValue(filter.term, filter.facet);
415
+ const facet = this.options.find(element => element.value === filter.facetPropPath);
416
+ if (facet) {
417
+ const filter = facet.children.find(element => element.value === item);
418
+ if (filter)
419
+ return true;
420
+ }
421
+ }
422
+ return false;
423
+ },
424
+ /**
425
+ * Return a list of valid filers given a list of filters,
426
+ */
427
+ getValidatedFilters: function (filters) {
428
+ if (filters) {
429
+ if (this.cascaderIsReady) {
430
+ const result = [];
431
+ filters.forEach(filter => {
432
+ if (this.validateFilter(filter)) {
433
+ result.push(filter);
434
+ }
435
+ });
436
+ return result;
437
+ } else return filters;
438
+ }
439
+ return [];
440
+ },
441
+ },
442
+ mounted: function () {
443
+ this.algoliaClient = new AlgoliaClient(this.envVars.ALGOLIA_ID, this.envVars.ALGOLIA_KEY, this.envVars.PENNSIEVE_API_LOCATION);
444
+ this.algoliaClient.initIndex(this.envVars.ALGOLIA_INDEX);
445
+ this.populateCascader().then(() => {
446
+ this.cascaderIsReady = true;
447
+ this.checkShowAllBoxes();
448
+ this.setCascader(this.entry.filterFacets);
449
+ this.makeCascadeLabelsClickable();
450
+ this.$emit("cascaderReady");
451
+ });
452
+ },
453
+ };
454
+ </script>
455
+
456
+ <!-- Add "scoped" attribute to limit CSS to this component only -->
457
+ <style scoped>
458
+ .filter-default-value {
459
+ pointer-events: none;
460
+ position: absolute;
461
+ top: 0;
462
+ left: 0;
463
+ padding-top: 10px;
464
+ padding-left: 16px;
465
+ }
466
+
467
+ .help {
468
+ width: 24px !important;
469
+ height: 24px;
470
+ transform: scale(1.1);
471
+ color: #8300bf;
472
+ cursor: pointer;
473
+ }
474
+
475
+ .popover {
476
+ color: rgb(48, 49, 51);
477
+ font-family: Asap;
478
+ margin: 12px;
479
+ }
480
+
481
+ .filter-icon-inside {
482
+ width: 12px !important;
483
+ height: 12px !important;
484
+ color: #292b66;
485
+ transform: scale(2) !important;
486
+ margin-bottom: 0px !important;
487
+ }
488
+
489
+ .cascader {
490
+ font-family: Asap;
491
+ font-size: 14px;
492
+ font-weight: 500;
493
+ font-stretch: normal;
494
+ font-style: normal;
495
+ line-height: normal;
496
+ letter-spacing: normal;
497
+ color: #292b66;
498
+ text-align: center;
499
+ padding-bottom: 6px;
500
+ }
501
+
502
+ .cascader >>> .el-cascder-panel {
503
+ max-height: 500px;
504
+ }
505
+
506
+ .cascader >>> .el-scrollbar__wrap {
507
+ overflow-x: hidden;
508
+ margin-bottom: 2px !important;
509
+ }
510
+
511
+ .cascader >>> li[aria-owns*="cascader"] > .el-checkbox {
512
+ display: none;
513
+ }
514
+
515
+ .dataset-results-feedback {
516
+ float: right;
517
+ text-align: right;
518
+ color: rgb(48, 49, 51);
519
+ font-family: Asap;
520
+ font-size: 18px;
521
+ font-weight: 500;
522
+ padding-top: 8px;
523
+ }
524
+
525
+ .search-filters {
526
+ position: relative;
527
+ float: left;
528
+ padding-right: 15px;
529
+ padding-bottom: 12px;
530
+ }
531
+
532
+ .number-shown-select {
533
+ float: right;
534
+ }
535
+
536
+ .number-shown-select >>> .el-input__inner {
537
+ width: 68px;
538
+ height: 40px;
539
+ color: rgb(48, 49, 51);
540
+ }
541
+
542
+ .search-filters >>> .el-cascader-node.is-active {
543
+ color: #8300bf;
544
+ }
545
+
546
+ .search-filters >>> .el-cascader-node.in-active-path {
547
+ color: #8300bf;
548
+ }
549
+
550
+ .search-filters >>> .el-checkbox__input.is-checked > .el-checkbox__inner {
551
+ background-color: #8300bf;
552
+ border-color: #8300bf;
553
+ }
554
+
555
+ .cascader >>> .el-cascader-menu:nth-child(2) .el-cascader-node:first-child {
556
+ border-bottom: 1px solid #e4e7ed;
557
+ }
558
+
559
+ .cascader >>> .el-cascader-node__label {
560
+ text-align: left;
561
+ }
562
+
563
+ .filters >>> .el-popover {
564
+ background: #f3ecf6 !important;
565
+ border: 1px solid #8300BF;
566
+ border-radius: 4px;
567
+ color: #303133 !important;
568
+ font-size: 12px;
569
+ line-height: 18px;
570
+
571
+
572
+ }
573
+
574
+ .filters >>> .el-popover[x-placement^="top"] .popper__arrow {
575
+ border-top-color: #8300BF;
576
+ border-bottom-width: 0;
577
+ }
578
+ .filters >>> .el-popover[x-placement^="top"] .popper__arrow::after {
579
+ border-top-color: #f3ecf6;
580
+ border-bottom-width: 0;
581
+ }
582
+
583
+ .filters >>> .el-popover[x-placement^="bottom"] .popper__arrow {
584
+ border-top-width: 0;
585
+ border-bottom-color: #8300BF;
586
+ }
587
+ .filters >>> .el-popover[x-placement^="bottom"] .popper__arrow::after {
588
+ border-top-width: 0;
589
+ border-bottom-color: #f3ecf6;
590
+ }
591
+
592
+ .filters >>> .el-popover[x-placement^="right"] .popper__arrow {
593
+ border-right-color: #8300BF;
594
+ border-left-width: 0;
595
+ }
596
+ .filters >>> .el-popover[x-placement^="right"] .popper__arrow::after {
597
+ border-right-color: #f3ecf6;
598
+ border-left-width: 0;
599
+ }
600
+
601
+ .filters >>> .el-popover[x-placement^="left"] .popper__arrow {
602
+ border-right-width: 0;
603
+ border-left-color: #8300BF;
604
+ }
605
+ .filters >>> .el-popover[x-placement^="left"] .popper__arrow::after {
606
+ border-right-width: 0;
607
+ border-left-color: #f3ecf6;
608
+ }
609
+ </style>