@abi-software/map-side-bar 1.5.0 → 1.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/CHANGELOG.md +228 -228
  2. package/LICENSE +201 -201
  3. package/README.md +146 -146
  4. package/babel.config.js +14 -14
  5. package/dist/map-side-bar.common.js +423 -182
  6. package/dist/map-side-bar.common.js.map +1 -1
  7. package/dist/map-side-bar.css +1 -1
  8. package/dist/map-side-bar.umd.js +423 -182
  9. package/dist/map-side-bar.umd.js.map +1 -1
  10. package/dist/map-side-bar.umd.min.js +1 -1
  11. package/dist/map-side-bar.umd.min.js.map +1 -1
  12. package/package-lock.json +14536 -14536
  13. package/package.json +75 -75
  14. package/public/index.html +17 -17
  15. package/src/App.vue +164 -164
  16. package/src/algolia/algolia.js +188 -188
  17. package/src/algolia/utils.js +69 -69
  18. package/src/assets/_variables.scss +43 -43
  19. package/src/assets/styles.scss +7 -7
  20. package/src/components/BadgesGroup.vue +144 -144
  21. package/src/components/Cascader.vue +49 -49
  22. package/src/components/ContextCard.vue +397 -397
  23. package/src/components/DatasetCard.vue +328 -328
  24. package/src/components/EventBus.js +3 -3
  25. package/src/components/ImageGallery.vue +531 -531
  26. package/src/components/SearchFilters.vue +586 -587
  27. package/src/components/SearchHistory.vue +146 -0
  28. package/src/components/SideBar.vue +235 -232
  29. package/src/components/SidebarContent.vue +564 -554
  30. package/src/components/Tabs.vue +78 -78
  31. package/src/components/hardcoded-context-info.js +79 -79
  32. package/src/components/index.js +8 -8
  33. package/src/components/species-map.js +8 -8
  34. package/src/main.js +8 -8
  35. package/src/mixins/S3Bucket.vue +31 -31
  36. package/static.json +6 -6
  37. package/vue.config.js +19 -19
@@ -1,587 +1,586 @@
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
- })
182
- .finally(() => {
183
- resolve();
184
- });
185
- });
186
- },
187
- tagsChangedCallback: function (presentTags) {
188
- if (presentTags.length > 0) {
189
- this.showFiltersText = false;
190
- } else {
191
- this.showFiltersText = true;
192
- }
193
- },
194
- // cascadeEvent: initiate searches based off cascader changes
195
- cascadeEvent: function (event) {
196
- if (event) {
197
- // Check for show all in selected cascade options
198
- event = this.showAllEventModifier(event);
199
-
200
- // Create results for the filter update
201
- let filterKeys = event.filter( selection => selection !== undefined).map( fs => ({
202
- facetPropPath: fs[0],
203
- facet: fs[1].split(">")[1],
204
- term: fs[1].split(">")[0],
205
- AND: fs[2] // for setting the boolean
206
- }))
207
-
208
- // Move results from arrays to object for use on scicrunch (note that we remove 'duplicate' as that is only needed for filter keys)
209
- let filters = event.filter( selection => selection !== undefined).map( fs => {
210
- let propPath = fs[0].includes('duplicate') ? fs[0].split('duplicate')[0] : fs[0]
211
- return {
212
- facetPropPath: propPath,
213
- facet: fs[1].split(">")[1],
214
- term: fs[1].split(">")[0],
215
- AND: fs[2] // for setting the boolean
216
- }
217
- })
218
-
219
-
220
- this.$emit('loading', true) // let sidebarcontent wait for the requests
221
-
222
- this.$emit("filterResults", filters); // emit filters for apps above sidebar
223
- this.setCascader(filterKeys); //update our cascader v-model if we modified the event
224
- this.makeCascadeLabelsClickable();
225
- }
226
- },
227
- // 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
228
- // *NOTE* Does NOT remove 'Show all' selections from showing in 'cascadeSelected'
229
- showAllEventModifier: function (event) {
230
- // check if show all is in the cascader checked option list
231
- let hasShowAll = event
232
- .map((ev) => (ev ? ev[1].toLowerCase().includes("show all") : false))
233
- .includes(true);
234
- // remove all selected options below the show all if checked
235
- if (hasShowAll) {
236
- let modifiedEvent = [];
237
- let facetMaps = {};
238
- //catagorised different facet items
239
- for (const i in event) {
240
- if (facetMaps[event[i][0]] === undefined) facetMaps[event[i][0]] = [];
241
- facetMaps[event[i][0]].push(event[i]);
242
- }
243
- // go through each facets
244
- for (const facet in facetMaps) {
245
- let showAll = undefined;
246
- // Find the show all item if any
247
- for (let i = facetMaps[facet].length - 1; i >= 0; i--) {
248
- if (facetMaps[facet][i][1].toLowerCase().includes("show all")) {
249
- //seperate the showAll item and the rest
250
- showAll = facetMaps[facet].splice(i, 1)[0];
251
- break;
252
- }
253
- }
254
- if (showAll) {
255
- if (this.previousShowAllChecked[facet]) {
256
- //Unset the show all if it was present previously
257
- //and there are other items
258
- if (facetMaps[facet].length > 0)
259
- modifiedEvent.push(...facetMaps[facet]);
260
- else modifiedEvent.push(showAll);
261
- } else {
262
- //showAll is turned on
263
- modifiedEvent.push(showAll);
264
- }
265
- } else {
266
- modifiedEvent.push(...facetMaps[facet]);
267
- }
268
- }
269
- //Make sure the expanded item are sorted first.
270
- return modifiedEvent.sort((a, b) => {
271
- if (this.__expandItem__) {
272
- if (a[0] == this.__expandItem__) {
273
- if (b[0] == this.__expandItem__) {
274
- return 0;
275
- } else {
276
- return -1;
277
- }
278
- } else if (b[0] == this.__expandItem__) {
279
- if (a[0] == this.__expandItem__) {
280
- return 0;
281
- } else {
282
- return 1;
283
- }
284
- } else {
285
- return 0;
286
- }
287
- } else return 0;
288
- });
289
- }
290
- return event;
291
- },
292
- cascadeExpandChange: function (event) {
293
- //work around as the expand item may change on modifying the cascade props
294
- this.__expandItem__ = event;
295
- this.makeCascadeLabelsClickable();
296
- },
297
- numberShownChanged: function (event) {
298
- this.$emit("numberPerPage", parseInt(event));
299
- },
300
- updatePreviousShowAllChecked: function (options) {
301
- //Reset the states
302
- for (const facet in this.previousShowAllChecked) {
303
- this.previousShowAllChecked[facet] = false;
304
- }
305
- options.forEach((element) => {
306
- if (element[1].toLowerCase().includes("show all"))
307
- this.previousShowAllChecked[element[0]] = true;
308
- });
309
- },
310
- // setCascader: Clears previous selections and takes in an array of facets to select: filterFacets
311
- // facets are in the form:
312
- // {
313
- // facetPropPath: 'anatomy.organ.name',
314
- // term: 'Sex',
315
- // facet: 'Male'
316
- // AND: true // Optional value for setting the boolean within a facet
317
- // }
318
- setCascader: function (filterFacets) {
319
- //Do not set the value unless it is ready
320
- if (this.cascaderIsReady && filterFacets && filterFacets.length != 0) {
321
- this.cascadeSelected = filterFacets.map(e => {
322
- return [
323
- e.facetPropPath,
324
- this.createCascaderItemValue(capitalise(e.term), e.facet),
325
- ]
326
- });
327
-
328
- // Unforttunately the cascader is very particular about it's v-model
329
- // to get around this we create a clone of it and use this clone for adding our boolean information
330
- this.cascadeSelectedWithBoolean= filterFacets.map(e => {
331
- return [
332
- e.facetPropPath,
333
- this.createCascaderItemValue(capitalise(e.term), e.facet),
334
- e.AND
335
- ]
336
- });
337
- this.updatePreviousShowAllChecked(this.cascadeSelected);
338
- }
339
- },
340
- addFilter: function (filter) {
341
- //Do not set the value unless it is ready
342
- if (this.cascaderIsReady && filter) {
343
- if (this.validateFilter(filter)) {
344
- this.cascadeSelected.filter(f=>f.term != filter.term)
345
- this.cascadeSelected.push([filter.facetPropPath, this.createCascaderItemValue(filter.term, filter.facet), filter.AND])
346
- this.cascadeSelectedWithBoolean.push([filter.facetPropPath, this.createCascaderItemValue(filter.term, filter.facet), filter.AND])
347
- // The 'AND' her is to set the boolean value when we search on the filters. It can be undefined without breaking anything
348
- return true;
349
- }
350
- }
351
- },
352
- initiateSearch: function() {
353
- this.cascadeEvent(this.cascadeSelectedWithBoolean)
354
- },
355
- // checkShowAllBoxes: Checks each 'Show all' cascade option by using the setCascader function
356
- checkShowAllBoxes: function(){
357
- this.setCascader(
358
- this.options.map(option => {
359
- return {
360
- facetPropPath: option.value,
361
- term: option.label,
362
- facet: 'Show all'
363
- }
364
- })
365
- )
366
- },
367
- makeCascadeLabelsClickable: function () {
368
- // Next tick allows the cascader menu to change
369
- this.$nextTick(() => {
370
- this.$refs.cascader.$el
371
- .querySelectorAll(".el-cascader-node__label")
372
- .forEach((el) => {
373
- // step through each cascade label
374
- el.onclick = function () {
375
- const checkbox = this.previousElementSibling;
376
- if (checkbox) {
377
- if (!checkbox.parentElement.attributes["aria-owns"]) {
378
- // check if we are at the lowest level of cascader
379
- this.previousElementSibling.click(); // Click the checkbox
380
- }
381
- }
382
- };
383
- });
384
- });
385
- },
386
- /**
387
- * Validate ther filter term to make sure the term is correct
388
- */
389
- validateFilter: function(filter) {
390
- if (filter && filter.facet && filter.term) {
391
- const item = this.createCascaderItemValue(filter.term, filter.facet);
392
- const facet = this.options.find(element => element.value === filter.facetPropPath);
393
- if (facet) {
394
- const filter = facet.children.find(element => element.value === item);
395
- if (filter)
396
- return true;
397
- }
398
- }
399
- return false;
400
- },
401
- /**
402
- * Return a list of valid filers given a list of filters,
403
- */
404
- getValidatedFilters: function (filters) {
405
- if (filters) {
406
- if (this.cascaderIsReady) {
407
- const result = [];
408
- filters.forEach(filter => {
409
- if (this.validateFilter(filter)) {
410
- result.push(filter);
411
- }
412
- });
413
- return result;
414
- } else return filters;
415
- }
416
- return [];
417
- },
418
- },
419
- mounted: function () {
420
- this.algoliaClient = new AlgoliaClient(this.envVars.ALGOLIA_ID, this.envVars.ALGOLIA_KEY, this.envVars.PENNSIEVE_API_LOCATION);
421
- this.algoliaClient.initIndex(this.envVars.ALGOLIA_INDEX);
422
- this.populateCascader().then(() => {
423
- this.cascaderIsReady = true;
424
- this.checkShowAllBoxes();
425
- this.setCascader(this.entry.filterFacets);
426
- this.makeCascadeLabelsClickable();
427
- this.$emit("cascaderReady");
428
- });
429
- },
430
- };
431
- </script>
432
-
433
- <!-- Add "scoped" attribute to limit CSS to this component only -->
434
- <style scoped lang="scss">
435
- @import "~element-ui/packages/theme-chalk/src/option";
436
- @import "~element-ui/packages/theme-chalk/src/popover";
437
- @import "~element-ui/packages/theme-chalk/src/select";
438
-
439
- .filter-default-value {
440
- pointer-events: none;
441
- position: absolute;
442
- top: 0;
443
- left: 0;
444
- padding-top: 10px;
445
- padding-left: 16px;
446
- }
447
-
448
- .help {
449
- width: 24px !important;
450
- height: 24px;
451
- transform: scale(1.1);
452
- cursor: pointer;
453
- }
454
-
455
- .popover {
456
- color: rgb(48, 49, 51);
457
- font-family: Asap;
458
- margin: 12px;
459
- }
460
-
461
- .filter-icon-inside {
462
- width: 12px !important;
463
- height: 12px !important;
464
- color: #292b66;
465
- transform: scale(2) !important;
466
- margin-bottom: 0px !important;
467
- }
468
-
469
- .cascader {
470
- font-family: Asap;
471
- font-size: 14px;
472
- font-weight: 500;
473
- font-stretch: normal;
474
- font-style: normal;
475
- line-height: normal;
476
- letter-spacing: normal;
477
- color: #292b66;
478
- text-align: center;
479
- padding-bottom: 6px;
480
- }
481
-
482
- .cascader ::v-deep .el-cascder-panel {
483
- max-height: 500px;
484
- }
485
-
486
- .cascader::v-deep .el-scrollbar__wrap {
487
- overflow-x: hidden;
488
- margin-bottom: 2px !important;
489
- }
490
-
491
- .cascader ::v-deep li[aria-owns*="cascader"] > .el-checkbox {
492
- display: none;
493
- }
494
-
495
- .dataset-results-feedback {
496
- float: right;
497
- text-align: right;
498
- color: rgb(48, 49, 51);
499
- font-family: Asap;
500
- font-size: 18px;
501
- font-weight: 500;
502
- padding-top: 8px;
503
- }
504
-
505
- .search-filters {
506
- position: relative;
507
- float: left;
508
- padding-right: 15px;
509
- padding-bottom: 12px;
510
- }
511
-
512
- .number-shown-select {
513
- float: right;
514
- }
515
-
516
- .number-shown-select ::v-deep .el-input__inner {
517
- width: 68px;
518
- height: 40px;
519
- color: rgb(48, 49, 51);
520
- }
521
-
522
- .search-filters ::v-deep .el-cascader-node.is-active {
523
- color: $app-primary-color;
524
- }
525
-
526
- .search-filters ::v-deep .el-cascader-node.in-active-path {
527
- color: $app-primary-color;
528
- }
529
-
530
- .search-filters ::v-deep .el-checkbox__input.is-checked > .el-checkbox__inner {
531
- background-color: $app-primary-color;
532
- border-color: $app-primary-color;
533
- }
534
-
535
- .cascader ::v-deep .el-cascader-menu:nth-child(2) .el-cascader-node:first-child {
536
- border-bottom: 1px solid #e4e7ed;
537
- }
538
-
539
- .cascader ::v-deep .el-cascader-node__label {
540
- text-align: left;
541
- }
542
-
543
- .filters ::v-deep .el-popover {
544
- background: #f3ecf6 !important;
545
- border: 1px solid $app-primary-color;
546
- border-radius: 4px;
547
- color: #303133 !important;
548
- font-size: 12px;
549
- line-height: 18px;
550
- }
551
-
552
- .filters ::v-deep .el-popover[x-placement^="top"] .popper__arrow {
553
- border-top-color: $app-primary-color;
554
- border-bottom-width: 0;
555
- }
556
- .filters ::v-deep .el-popover[x-placement^="top"] .popper__arrow::after {
557
- border-top-color: #f3ecf6;
558
- border-bottom-width: 0;
559
- }
560
-
561
- .filters ::v-deep .el-popover[x-placement^="bottom"] .popper__arrow {
562
- border-top-width: 0;
563
- border-bottom-color: $app-primary-color;
564
- }
565
- .filters ::v-deep .el-popover[x-placement^="bottom"] .popper__arrow::after {
566
- border-top-width: 0;
567
- border-bottom-color: #f3ecf6;
568
- }
569
-
570
- .filters ::v-deep .el-popover[x-placement^="right"] .popper__arrow {
571
- border-right-color: $app-primary-color;
572
- border-left-width: 0;
573
- }
574
- .filters ::v-deep .el-popover[x-placement^="right"] .popper__arrow::after {
575
- border-right-color: #f3ecf6;
576
- border-left-width: 0;
577
- }
578
-
579
- .filters ::v-deep .el-popover[x-placement^="left"] .popper__arrow {
580
- border-right-width: 0;
581
- border-left-color: $app-primary-color;
582
- }
583
- .filters ::v-deep .el-popover[x-placement^="left"] .popper__arrow::after {
584
- border-right-width: 0;
585
- border-left-color: #f3ecf6;
586
- }
587
- </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
+ })
182
+ .finally(() => {
183
+ resolve();
184
+ });
185
+ });
186
+ },
187
+ tagsChangedCallback: function (presentTags) {
188
+ if (presentTags.length > 0) {
189
+ this.showFiltersText = false;
190
+ } else {
191
+ this.showFiltersText = true;
192
+ }
193
+ },
194
+ // cascadeEvent: initiate searches based off cascader changes
195
+ cascadeEvent: function (event) {
196
+ if (event) {
197
+ // Check for show all in selected cascade options
198
+ event = this.showAllEventModifier(event);
199
+
200
+ // Create results for the filter update
201
+ let filterKeys = event.filter( selection => selection !== undefined).map( fs => ({
202
+ facetPropPath: fs[0],
203
+ facet: fs[1].split(">")[1],
204
+ term: fs[1].split(">")[0],
205
+ AND: fs[2] // for setting the boolean
206
+ }))
207
+
208
+ // Move results from arrays to object for use on scicrunch (note that we remove 'duplicate' as that is only needed for filter keys)
209
+ let filters = event.filter( selection => selection !== undefined).map( fs => {
210
+ let propPath = fs[0].includes('duplicate') ? fs[0].split('duplicate')[0] : fs[0]
211
+ return {
212
+ facetPropPath: propPath,
213
+ facet: fs[1].split(">")[1],
214
+ term: fs[1].split(">")[0],
215
+ AND: fs[2] // for setting the boolean
216
+ }
217
+ })
218
+
219
+
220
+ this.$emit('loading', true) // let sidebarcontent wait for the requests
221
+
222
+ this.$emit("filterResults", filters); // emit filters for apps above sidebar
223
+ this.setCascader(filterKeys); //update our cascader v-model if we modified the event
224
+ this.makeCascadeLabelsClickable();
225
+ }
226
+ },
227
+ // 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
228
+ // *NOTE* Does NOT remove 'Show all' selections from showing in 'cascadeSelected'
229
+ showAllEventModifier: function (event) {
230
+ // check if show all is in the cascader checked option list
231
+ let hasShowAll = event
232
+ .map((ev) => (ev ? ev[1].toLowerCase().includes("show all") : false))
233
+ .includes(true);
234
+ // remove all selected options below the show all if checked
235
+ if (hasShowAll) {
236
+ let modifiedEvent = [];
237
+ let facetMaps = {};
238
+ //catagorised different facet items
239
+ for (const i in event) {
240
+ if (facetMaps[event[i][0]] === undefined) facetMaps[event[i][0]] = [];
241
+ facetMaps[event[i][0]].push(event[i]);
242
+ }
243
+ // go through each facets
244
+ for (const facet in facetMaps) {
245
+ let showAll = undefined;
246
+ // Find the show all item if any
247
+ for (let i = facetMaps[facet].length - 1; i >= 0; i--) {
248
+ if (facetMaps[facet][i][1].toLowerCase().includes("show all")) {
249
+ //seperate the showAll item and the rest
250
+ showAll = facetMaps[facet].splice(i, 1)[0];
251
+ break;
252
+ }
253
+ }
254
+ if (showAll) {
255
+ if (this.previousShowAllChecked[facet]) {
256
+ //Unset the show all if it was present previously
257
+ //and there are other items
258
+ if (facetMaps[facet].length > 0)
259
+ modifiedEvent.push(...facetMaps[facet]);
260
+ else modifiedEvent.push(showAll);
261
+ } else {
262
+ //showAll is turned on
263
+ modifiedEvent.push(showAll);
264
+ }
265
+ } else {
266
+ modifiedEvent.push(...facetMaps[facet]);
267
+ }
268
+ }
269
+ //Make sure the expanded item are sorted first.
270
+ return modifiedEvent.sort((a, b) => {
271
+ if (this.__expandItem__) {
272
+ if (a[0] == this.__expandItem__) {
273
+ if (b[0] == this.__expandItem__) {
274
+ return 0;
275
+ } else {
276
+ return -1;
277
+ }
278
+ } else if (b[0] == this.__expandItem__) {
279
+ if (a[0] == this.__expandItem__) {
280
+ return 0;
281
+ } else {
282
+ return 1;
283
+ }
284
+ } else {
285
+ return 0;
286
+ }
287
+ } else return 0;
288
+ });
289
+ }
290
+ return event;
291
+ },
292
+ cascadeExpandChange: function (event) {
293
+ //work around as the expand item may change on modifying the cascade props
294
+ this.__expandItem__ = event;
295
+ this.makeCascadeLabelsClickable();
296
+ },
297
+ numberShownChanged: function (event) {
298
+ this.$emit("numberPerPage", parseInt(event));
299
+ },
300
+ updatePreviousShowAllChecked: function (options) {
301
+ //Reset the states
302
+ for (const facet in this.previousShowAllChecked) {
303
+ this.previousShowAllChecked[facet] = false;
304
+ }
305
+ options.forEach((element) => {
306
+ if (element[1].toLowerCase().includes("show all"))
307
+ this.previousShowAllChecked[element[0]] = true;
308
+ });
309
+ },
310
+ // setCascader: Clears previous selections and takes in an array of facets to select: filterFacets
311
+ // facets are in the form:
312
+ // {
313
+ // facetPropPath: 'anatomy.organ.name',
314
+ // term: 'Sex',
315
+ // facet: 'Male'
316
+ // AND: true // Optional value for setting the boolean within a facet
317
+ // }
318
+ setCascader: function (filterFacets) {
319
+ //Do not set the value unless it is ready
320
+ if (this.cascaderIsReady && filterFacets && filterFacets.length != 0) {
321
+ this.cascadeSelected = filterFacets.map(e => {
322
+ return [
323
+ e.facetPropPath,
324
+ this.createCascaderItemValue(capitalise(e.term), e.facet),
325
+ ]
326
+ });
327
+
328
+ // Unforttunately the cascader is very particular about it's v-model
329
+ // to get around this we create a clone of it and use this clone for adding our boolean information
330
+ this.cascadeSelectedWithBoolean= filterFacets.map(e => {
331
+ return [
332
+ e.facetPropPath,
333
+ this.createCascaderItemValue(capitalise(e.term), e.facet),
334
+ e.AND
335
+ ]
336
+ });
337
+ this.updatePreviousShowAllChecked(this.cascadeSelected);
338
+ }
339
+ },
340
+ addFilter: function (filter) {
341
+ //Do not set the value unless it is ready
342
+ if (this.cascaderIsReady && filter) {
343
+ if (this.validateFilter(filter)) {
344
+ this.cascadeSelected.filter(f=>f.term != filter.term)
345
+ this.cascadeSelected.push([filter.facetPropPath, this.createCascaderItemValue(filter.term, filter.facet), filter.AND])
346
+ this.cascadeSelectedWithBoolean.push([filter.facetPropPath, this.createCascaderItemValue(filter.term, filter.facet), filter.AND])
347
+ // The 'AND' her is to set the boolean value when we search on the filters. It can be undefined without breaking anything
348
+ return true;
349
+ }
350
+ }
351
+ },
352
+ initiateSearch: function() {
353
+ this.cascadeEvent(this.cascadeSelectedWithBoolean)
354
+ },
355
+ // checkShowAllBoxes: Checks each 'Show all' cascade option by using the setCascader function
356
+ checkShowAllBoxes: function(){
357
+ this.setCascader(
358
+ this.options.map(option => {
359
+ return {
360
+ facetPropPath: option.value,
361
+ term: option.label,
362
+ facet: 'Show all'
363
+ }
364
+ })
365
+ )
366
+ },
367
+ makeCascadeLabelsClickable: function () {
368
+ // Next tick allows the cascader menu to change
369
+ this.$nextTick(() => {
370
+ this.$refs.cascader.$el
371
+ .querySelectorAll(".el-cascader-node__label")
372
+ .forEach((el) => {
373
+ // step through each cascade label
374
+ el.onclick = function () {
375
+ const checkbox = this.previousElementSibling;
376
+ if (checkbox) {
377
+ if (!checkbox.parentElement.attributes["aria-owns"]) {
378
+ // check if we are at the lowest level of cascader
379
+ this.previousElementSibling.click(); // Click the checkbox
380
+ }
381
+ }
382
+ };
383
+ });
384
+ });
385
+ },
386
+ /**
387
+ * Validate ther filter term to make sure the term is correct
388
+ */
389
+ validateFilter: function(filter) {
390
+ if (filter && filter.facet && filter.term) {
391
+ const item = this.createCascaderItemValue(filter.term, filter.facet);
392
+ const facet = this.options.find(element => element.value === filter.facetPropPath);
393
+ if (facet) {
394
+ const filter = facet.children.find(element => element.value === item);
395
+ if (filter)
396
+ return true;
397
+ }
398
+ }
399
+ return false;
400
+ },
401
+ /**
402
+ * Return a list of valid filers given a list of filters,
403
+ */
404
+ getValidatedFilters: function (filters) {
405
+ if (filters) {
406
+ if (this.cascaderIsReady) {
407
+ const result = [];
408
+ filters.forEach(filter => {
409
+ if (this.validateFilter(filter)) {
410
+ result.push(filter);
411
+ }
412
+ });
413
+ return result;
414
+ } else return filters;
415
+ }
416
+ return [];
417
+ },
418
+ },
419
+ mounted: function () {
420
+ this.algoliaClient = new AlgoliaClient(this.envVars.ALGOLIA_ID, this.envVars.ALGOLIA_KEY, this.envVars.PENNSIEVE_API_LOCATION);
421
+ this.algoliaClient.initIndex(this.envVars.ALGOLIA_INDEX);
422
+ this.populateCascader().then(() => {
423
+ this.cascaderIsReady = true;
424
+ this.checkShowAllBoxes();
425
+ this.setCascader(this.entry.filterFacets);
426
+ this.makeCascadeLabelsClickable();
427
+ this.$emit("cascaderReady");
428
+ });
429
+ },
430
+ };
431
+ </script>
432
+
433
+ <!-- Add "scoped" attribute to limit CSS to this component only -->
434
+ <style scoped lang="scss">
435
+ @import "~element-ui/packages/theme-chalk/src/option";
436
+ @import "~element-ui/packages/theme-chalk/src/popover";
437
+ @import "~element-ui/packages/theme-chalk/src/select";
438
+
439
+ .filter-default-value {
440
+ pointer-events: none;
441
+ position: absolute;
442
+ top: 0;
443
+ left: 0;
444
+ padding-top: 10px;
445
+ padding-left: 16px;
446
+ }
447
+
448
+ .help {
449
+ width: 24px !important;
450
+ height: 24px;
451
+ transform: scale(1.1);
452
+ cursor: pointer;
453
+ }
454
+
455
+ .popover {
456
+ color: rgb(48, 49, 51);
457
+ font-family: Asap;
458
+ margin: 12px;
459
+ }
460
+
461
+ .filter-icon-inside {
462
+ width: 12px !important;
463
+ height: 12px !important;
464
+ color: #292b66;
465
+ transform: scale(2) !important;
466
+ margin-bottom: 0px !important;
467
+ }
468
+
469
+ .cascader {
470
+ font-family: Asap;
471
+ font-size: 14px;
472
+ font-weight: 500;
473
+ font-stretch: normal;
474
+ font-style: normal;
475
+ line-height: normal;
476
+ letter-spacing: normal;
477
+ color: #292b66;
478
+ text-align: center;
479
+ padding-bottom: 6px;
480
+ }
481
+
482
+ .cascader ::v-deep .el-cascder-panel {
483
+ max-height: 500px;
484
+ }
485
+
486
+ .cascader::v-deep .el-scrollbar__wrap {
487
+ overflow-x: hidden;
488
+ margin-bottom: 2px !important;
489
+ }
490
+
491
+ .cascader ::v-deep li[aria-owns*="cascader"] > .el-checkbox {
492
+ display: none;
493
+ }
494
+
495
+ .dataset-results-feedback {
496
+ float: right;
497
+ text-align: right;
498
+ color: rgb(48, 49, 51);
499
+ font-family: Asap;
500
+ font-size: 18px;
501
+ font-weight: 500;
502
+ padding-top: 8px;
503
+ }
504
+
505
+ .search-filters {
506
+ position: relative;
507
+ float: left;
508
+ padding-right: 15px;
509
+ }
510
+
511
+ .number-shown-select {
512
+ float: right;
513
+ }
514
+
515
+ .number-shown-select ::v-deep .el-input__inner {
516
+ width: 68px;
517
+ height: 40px;
518
+ color: rgb(48, 49, 51);
519
+ }
520
+
521
+ .search-filters ::v-deep .el-cascader-node.is-active {
522
+ color: $app-primary-color;
523
+ }
524
+
525
+ .search-filters ::v-deep .el-cascader-node.in-active-path {
526
+ color: $app-primary-color;
527
+ }
528
+
529
+ .search-filters ::v-deep .el-checkbox__input.is-checked > .el-checkbox__inner {
530
+ background-color: $app-primary-color;
531
+ border-color: $app-primary-color;
532
+ }
533
+
534
+ .cascader ::v-deep .el-cascader-menu:nth-child(2) .el-cascader-node:first-child {
535
+ border-bottom: 1px solid #e4e7ed;
536
+ }
537
+
538
+ .cascader ::v-deep .el-cascader-node__label {
539
+ text-align: left;
540
+ }
541
+
542
+ .filters ::v-deep .el-popover {
543
+ background: #f3ecf6 !important;
544
+ border: 1px solid $app-primary-color;
545
+ border-radius: 4px;
546
+ color: #303133 !important;
547
+ font-size: 12px;
548
+ line-height: 18px;
549
+ }
550
+
551
+ .filters ::v-deep .el-popover[x-placement^="top"] .popper__arrow {
552
+ border-top-color: $app-primary-color;
553
+ border-bottom-width: 0;
554
+ }
555
+ .filters ::v-deep .el-popover[x-placement^="top"] .popper__arrow::after {
556
+ border-top-color: #f3ecf6;
557
+ border-bottom-width: 0;
558
+ }
559
+
560
+ .filters ::v-deep .el-popover[x-placement^="bottom"] .popper__arrow {
561
+ border-top-width: 0;
562
+ border-bottom-color: $app-primary-color;
563
+ }
564
+ .filters ::v-deep .el-popover[x-placement^="bottom"] .popper__arrow::after {
565
+ border-top-width: 0;
566
+ border-bottom-color: #f3ecf6;
567
+ }
568
+
569
+ .filters ::v-deep .el-popover[x-placement^="right"] .popper__arrow {
570
+ border-right-color: $app-primary-color;
571
+ border-left-width: 0;
572
+ }
573
+ .filters ::v-deep .el-popover[x-placement^="right"] .popper__arrow::after {
574
+ border-right-color: #f3ecf6;
575
+ border-left-width: 0;
576
+ }
577
+
578
+ .filters ::v-deep .el-popover[x-placement^="left"] .popper__arrow {
579
+ border-right-width: 0;
580
+ border-left-color: $app-primary-color;
581
+ }
582
+ .filters ::v-deep .el-popover[x-placement^="left"] .popper__arrow::after {
583
+ border-right-width: 0;
584
+ border-left-color: #f3ecf6;
585
+ }
586
+ </style>