@abi-software/map-side-bar 2.0.0-response.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/.eslintrc.js +12 -0
  2. package/.postcssrc.json +6 -0
  3. package/README.md +49 -27
  4. package/dist/favicon.ico +0 -0
  5. package/dist/map-side-bar.js +21139 -0
  6. package/dist/map-side-bar.umd.cjs +462 -0
  7. package/dist/style.css +1 -0
  8. package/package.json +48 -45
  9. package/src/App.vue +121 -55
  10. package/src/algolia/algolia.js +36 -9
  11. package/src/algolia/utils.js +45 -13
  12. package/src/assets/styles.scss +0 -1
  13. package/src/components/BadgesGroup.vue +56 -80
  14. package/src/components/DatasetCard.vue +143 -143
  15. package/src/components/EventBus.js +2 -2
  16. package/src/components/ImageGallery.vue +184 -228
  17. package/src/components/SearchFilters.vue +674 -289
  18. package/src/components/SearchHistory.vue +175 -0
  19. package/src/components/SideBar.vue +183 -97
  20. package/src/components/SidebarContent.vue +233 -241
  21. package/src/components/Tabs.vue +24 -24
  22. package/src/components.d.ts +35 -0
  23. package/src/main.js +6 -5
  24. package/src/mixins/S3Bucket.vue +6 -0
  25. package/vite.config.js +56 -0
  26. package/vuese-generator.js +65 -0
  27. package/babel.config.js +0 -14
  28. package/dist/demo.html +0 -10
  29. package/dist/fonts/element-icons.535877f5.woff +0 -0
  30. package/dist/fonts/element-icons.732389de.ttf +0 -0
  31. package/dist/img/missing-image.1878d8b8.svg +0 -1
  32. package/dist/map-side-bar.common.js +0 -7794
  33. package/dist/map-side-bar.common.js.map +0 -1
  34. package/dist/map-side-bar.css +0 -1
  35. package/dist/map-side-bar.umd.js +0 -7804
  36. package/dist/map-side-bar.umd.js.map +0 -1
  37. package/dist/map-side-bar.umd.min.js +0 -2
  38. package/dist/map-side-bar.umd.min.js.map +0 -1
  39. package/package-lock.json +0 -14428
  40. package/public/index.html +0 -17
  41. package/src/components/Cascader.vue +0 -49
  42. package/src/components/ContextCard.vue +0 -397
  43. package/src/components/hardcoded-context-info.js +0 -80
  44. package/src/demo/AlternateResponse.js +0 -141
  45. package/vue.config.js +0 -21
@@ -1,105 +1,146 @@
1
1
  <template>
2
2
  <div class="filters">
3
- <map-svg-sprite-color />
3
+ <MapSvgSpriteColor />
4
+ <div class="cascader-tag" v-if="presentTags.length > 0">
5
+ <el-tag
6
+ class="ml-2"
7
+ type="info"
8
+ closable
9
+ @close="cascadeTagClose(presentTags[0])"
10
+ >
11
+ {{ presentTags[0] }}
12
+ </el-tag>
13
+ <el-popover
14
+ v-if="presentTags.length > 1"
15
+ placement="bottom-start"
16
+ :width="200"
17
+ trigger="hover"
18
+ >
19
+ <template #default>
20
+ <div class="el-tags-container">
21
+ <el-tag
22
+ v-for="(tag, i) in presentTags.slice(1)"
23
+ :key="i"
24
+ class="ml-2"
25
+ type="info"
26
+ closable
27
+ @close="cascadeTagClose(tag)"
28
+ >
29
+ {{ tag }}
30
+ </el-tag>
31
+ </div>
32
+ </template>
33
+ <template #reference>
34
+ <div class="el-tags-container">
35
+ <el-tag
36
+ v-if="presentTags.length > 1"
37
+ class="ml-2"
38
+ type="info"
39
+ >
40
+ +{{ presentTags.length - 1 }}
41
+ </el-tag>
42
+ </div>
43
+ </template>
44
+ </el-popover>
45
+ </div>
4
46
  <transition name="el-zoom-in-top">
5
- <span v-show="showFilters" class="search-filters transition-box">
6
- <custom-cascader
47
+ <span v-show="showFilters" v-loading="!cascaderIsReady" class="search-filters transition-box">
48
+ <el-cascader
7
49
  class="cascader"
8
50
  ref="cascader"
9
51
  v-model="cascadeSelected"
10
- placeholder
52
+ size="large"
53
+ placeholder=" "
11
54
  :collapse-tags="true"
55
+ collapse-tags-tooltip
12
56
  :options="options"
13
57
  :props="props"
14
58
  @change="cascadeEvent($event)"
15
59
  @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>
60
+ :show-all-levels="true"
61
+ popper-class="sidebar-cascader-popper"
62
+ />
63
+ <div v-if="showFiltersText" class="filter-default-value">Filters</div>
23
64
  <el-popover
24
65
  title="How do filters work?"
25
66
  width="250"
26
67
  trigger="hover"
27
- :append-to-body=false
68
+ :append-to-body="false"
28
69
  popper-class="popover"
29
- >
30
- <map-svg-icon slot="reference" icon="help" class="help"/>
31
- <div >
32
- <strong>Within categories:</strong> OR
33
- <br/>
70
+ >
71
+ <template #reference>
72
+ <MapSvgIcon icon="help" class="help" />
73
+ </template>
74
+ <div>
75
+ <strong>Within categories:</strong> OR
76
+ <br />
34
77
  example: 'heart' OR 'colon'
35
- <br/>
36
- <br/>
78
+ <br />
79
+ <br />
37
80
  <strong>Between categories:</strong> AND
38
- <br/>
81
+ <br />
39
82
  example: 'rat' AND 'lung'
40
83
  </div>
41
84
  </el-popover>
42
-
43
85
  </span>
44
86
  </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>
87
+ <div class="dataset-shown">
88
+ <span class="dataset-results-feedback">{{ numberOfResultsText }}</span>
89
+ <el-select
90
+ class="number-shown-select"
91
+ v-model="numberShown"
92
+ placeholder="10"
93
+ @change="numberShownChanged($event)"
94
+ >
95
+ <el-option
96
+ v-for="item in numberDatasetsShown"
97
+ :key="item"
98
+ :label="item"
99
+ :value="item"
100
+ ></el-option>
101
+ </el-select>
102
+ </div>
60
103
  </div>
61
104
  </template>
62
105
 
63
-
64
106
  <script>
65
107
  /* 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";
108
+ import { markRaw } from 'vue'
109
+ import {
110
+ ElOption as Option,
111
+ ElSelect as Select,
112
+ ElPopover as Popover,
113
+ ElCascader as Cascader,
114
+ } from 'element-plus'
115
+ import speciesMap from './species-map.js'
72
116
  import { MapSvgIcon, MapSvgSpriteColor } from "@abi-software/svg-sprite";
117
+ import '@abi-software/svg-sprite/dist/style.css'
73
118
 
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)
119
+ import { AlgoliaClient } from '../algolia/algolia.js'
120
+ import { facetPropPathMapping } from '../algolia/utils.js'
81
121
 
82
122
  const capitalise = function (txt) {
83
- return txt.charAt(0).toUpperCase() + txt.slice(1);
84
- };
123
+ return txt.charAt(0).toUpperCase() + txt.slice(1)
124
+ }
85
125
 
86
126
  const convertReadableLabel = function (original) {
87
- const name = original.toLowerCase();
127
+ const name = original.toLowerCase()
88
128
  if (speciesMap[name]) {
89
- return capitalise(speciesMap[name]);
129
+ return capitalise(speciesMap[name])
90
130
  } else {
91
- return capitalise(name);
131
+ return capitalise(name)
92
132
  }
93
- };
94
-
95
-
133
+ }
96
134
 
97
135
  export default {
98
- name: "SearchFilters",
136
+ name: 'SearchFilters',
99
137
  components: {
100
- CustomCascader,
101
138
  MapSvgIcon,
102
139
  MapSvgSpriteColor,
140
+ Option,
141
+ Select,
142
+ Popover,
143
+ Cascader
103
144
  },
104
145
  props: {
105
146
  /**
@@ -109,12 +150,7 @@ export default {
109
150
  entry: Object,
110
151
  envVars: {
111
152
  type: Object,
112
- default: ()=>{}
113
- },
114
- },
115
- inject: {
116
- 'alternateSearch' : {
117
- default: undefined,
153
+ default: () => {},
118
154
  },
119
155
  },
120
156
  data: function () {
@@ -129,152 +165,375 @@ export default {
129
165
  showFilters: true,
130
166
  showFiltersText: true,
131
167
  cascadeSelected: [],
132
- cascadeSelectedWithBoolean: [],
168
+ cascadeSelectedWithBoolean: [],
133
169
  numberShown: 10,
134
170
  filters: [],
135
- numberDatasetsShown: ["10", "20", "50"],
171
+ facets: ['Species', 'Gender', 'Organ', 'Datasets'],
172
+ numberDatasetsShown: ['10', '20', '50'],
136
173
  props: { multiple: true },
137
174
  options: [
138
175
  {
139
- value: "Species",
140
- label: "Species",
176
+ value: 'Species',
177
+ label: 'Species',
141
178
  children: [{}],
142
179
  },
143
180
  ],
144
- };
181
+ presentTags:[],
182
+ }
183
+ },
184
+ setup() {
185
+ const cascaderTags = markRaw({});
186
+ const correctnessCheck = markRaw({
187
+ term: new Set(),
188
+ facet: new Set(),
189
+ facet2: new Set()
190
+ });
191
+ return { cascaderTags, correctnessCheck }
145
192
  },
146
193
  computed: {
147
194
  numberOfResultsText: function () {
148
- return `${this.entry.numberOfHits} results | Showing`;
195
+ return `${this.entry.numberOfHits} results | Showing`
149
196
  },
150
197
  },
151
198
  methods: {
152
- createCascaderItemValue: function (term, facet) {
153
- if (facet) return term + ">" + facet;
154
- else return term;
155
- },
156
- populateCascaderOptions: function(data) {
157
- this.options = data;
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
- });
199
+ createCascaderItemValue: function (
200
+ term,
201
+ facet1 = undefined,
202
+ facet2 = undefined
203
+ ) {
204
+ let value = term
205
+ if (facet1) value = `${term}>${facet1}`
206
+ if (facet1 && facet2) value = `${term}>${facet1}>${facet2}`
207
+ if (!facet1 && facet2)
208
+ console.warn(
209
+ `Warning: ${facet2} provided without its parent, this will not be shown in the cascader`
210
+ )
211
+ return value
181
212
  },
182
- populateDefaultCascader: function() {
213
+ populateCascader: function () {
183
214
  return new Promise((resolve) => {
184
215
  // Algolia facet serach
185
- this.algoliaClient.getAlgoliaFacets(facetPropPathMapping)
216
+ this.algoliaClient
217
+ .getAlgoliaFacets(facetPropPathMapping)
186
218
  .then((data) => {
187
- this.populateCascaderOptions(data);
219
+ this.facets = data
220
+ this.options = data
221
+
222
+ // create top level of options in cascader
223
+ this.options.forEach((facet, i) => {
224
+ this.options[i].total = this.countTotalFacet(facet)
225
+
226
+ this.options[i].label = convertReadableLabel(facet.label)
227
+ this.options[i].value = this.createCascaderItemValue(
228
+ facet.key,
229
+ undefined
230
+ )
231
+
232
+ // put "Show all" as first option
233
+ this.options[i].children.unshift({
234
+ value: this.createCascaderItemValue('Show all'),
235
+ label: 'Show all',
236
+ })
237
+
238
+ // populate second level of options
239
+ this.options[i].children.forEach((facetItem, j) => {
240
+ this.options[i].children[j].label = convertReadableLabel(
241
+ facetItem.label
242
+ )
243
+ this.options[i].children[j].value =
244
+ this.createCascaderItemValue(facet.label, facetItem.label)
245
+ if (
246
+ this.options[i].children[j].children &&
247
+ this.options[i].children[j].children.length > 0
248
+ ) {
249
+ this.options[i].children[j].children.forEach((term, k) => {
250
+ this.options[i].children[j].children[k].label =
251
+ convertReadableLabel(term.label)
252
+ this.options[i].children[j].children[k].value =
253
+ this.createCascaderItemValue(
254
+ facet.label,
255
+ facetItem.label,
256
+ term.label
257
+ )
258
+ })
259
+ }
260
+ })
261
+ })
188
262
  })
189
263
  .finally(() => {
190
- resolve();
191
- });
192
- });
193
- },
194
- setCascaderReady:function() {
195
- this.cascaderIsReady = true;
196
- this.checkShowAllBoxes();
197
- this.setCascader(this.entry.filterFacets);
198
- this.makeCascadeLabelsClickable();
199
- this.$emit("cascaderReady");
264
+ resolve()
265
+ })
266
+ })
200
267
  },
201
- alternateSearchCB: function(payload) {
202
- this.populateCascaderOptions(payload.data);
203
- this.setCascaderReady();
268
+ /**
269
+ * Create manual events when cascader tag is closed
270
+ */
271
+ cascadeTagClose: function (tag) {
272
+ let manualEvent = []
273
+
274
+ Object.entries(this.cascaderTags).map((entry) => {
275
+ const term = entry[0], facet = entry[1] // Either "Array" or "Object", depends on the cascader item level
276
+ const option = this.options.filter((option) => option.label == term)[0]
277
+ const key = option.key
278
+
279
+ for (let index = 0; index < option.children.length; index++) {
280
+ const child = option.children[index]
281
+ const label = child.label, value = child.value
282
+
283
+ if (Array.isArray(facet)) {
284
+ // push "Show all" if there is no item checked
285
+ if (facet.length === 0 && label.toLowerCase() === "show all") {
286
+ manualEvent.push([key, value])
287
+ break
288
+ // push all checked items
289
+ } else if (label !== tag && facet.includes(label))
290
+ manualEvent.push([key, value])
291
+ } else {
292
+ // loop nested cascader items
293
+ Object.entries(facet).map((entry2) => {
294
+ const term2 = entry2[0], facet2 = entry2[1] // object key, object value
295
+
296
+ if (term2 === label) {
297
+ child.children.map((child2) => {
298
+ const label2 = child2.label, value2 = child2.value
299
+ // push all checked items
300
+ if (label2 !== tag && facet2.includes(label2))
301
+ manualEvent.push([key, value2])
302
+ })
303
+ }
304
+ })
305
+ }
306
+ }
307
+ })
308
+ this.cascadeEvent(manualEvent)
204
309
  },
205
- populateCascader: function () {
206
- if (this.alternateSearch) {
207
- this.alternateSearch({requestType: 'getFacets'}, this.alternateSearchCB);
208
- } else {
209
- this.populateDefaultCascader().then(() => {
210
- this.setCascaderReady();
211
- });
310
+ /**
311
+ * Re-generate 'cascaderTags' and 'presentTags'
312
+ * Not able to avoid wrong facet at the moment
313
+ */
314
+ tagsChangedCallback: function (event) {
315
+ if (this.correctnessCheck.term && this.correctnessCheck.facet && this.correctnessCheck.facet2) {
316
+ this.options.map((option) => {
317
+ this.correctnessCheck.term.add(option.label)
318
+ option.children.map((child) => {
319
+ this.correctnessCheck.facet.add(child.label)
320
+ if (option.label === 'Anatomical structure' && child.label !== 'Show all') {
321
+ child.children.map((child2) => {
322
+ this.correctnessCheck.facet2.add(child2.label)
323
+ })
324
+ }
325
+ })
326
+ })
212
327
  }
328
+
329
+ this.cascaderTags = {}
330
+ this.presentTags = []
331
+ event.map((item) => {
332
+ const { facet, facet2, term } = item
333
+ if (this.correctnessCheck.term.has(term) && this.correctnessCheck.facet.has(facet)) {
334
+ if (facet2) {
335
+ if (this.correctnessCheck.facet2.has(facet2)) {
336
+ if (term in this.cascaderTags) {
337
+ if (facet in this.cascaderTags[term]) this.cascaderTags[term][facet].push(facet2)
338
+ else this.cascaderTags[term][facet] = [facet2]
339
+ } else {
340
+ this.cascaderTags[term] = {}
341
+ this.cascaderTags[term][facet] = [facet2]
342
+ }
343
+ }
344
+ } else {
345
+ // If 'cascaderTags' has key 'Anatomical structure',
346
+ // it's value type will be Object (because it has nested facets),
347
+ // in this case 'push' action will not available.
348
+ if (term in this.cascaderTags && term !== 'Anatomical structure')
349
+ this.cascaderTags[term].push(facet)
350
+ else {
351
+ if (facet.toLowerCase() !== "show all") this.cascaderTags[term] = [facet]
352
+ else this.cascaderTags[term] = []
353
+ }
354
+ }
355
+ }
356
+ })
357
+
358
+ Object.values(this.cascaderTags).map((value) => {
359
+ const extend = Array.isArray(value) ? value : Object.values(value).flat(1)
360
+ this.presentTags = [...this.presentTags, ...extend]
361
+ })
362
+ this.presentTags = [...new Set(this.presentTags)]
363
+ if (this.presentTags.length > 0) this.showFiltersText = false
364
+ else this.showFiltersText = true
213
365
  },
214
- tagsChangedCallback: function (presentTags) {
215
- if (presentTags.length > 0) {
216
- this.showFiltersText = false;
217
- } else {
218
- this.showFiltersText = true;
366
+ /**
367
+ * Support for function 'showAllEventModifierForAutoCheckAll'
368
+ * Called in function 'populateCascader'
369
+ */
370
+ countTotalFacet: function (facet) {
371
+ if (['anatomy.organ.category.name'].includes(facet.key)) {
372
+ const count = facet.children.reduce((total, num) => {
373
+ // The first 'total' will be an object
374
+ total = typeof total == 'number' ? total : total.children.length
375
+ return total + num.children.length
376
+ })
377
+ return count
219
378
  }
379
+ return facet.children.length
380
+ },
381
+ /**
382
+ * When check/uncheck all child items, automatically check "Show all"
383
+ */
384
+ showAllEventModifierForAutoCheckAll: function (event) {
385
+ const currentKeys = {}
386
+ event.map((e) => {
387
+ const eventKey = e[0]
388
+ if (eventKey in currentKeys) currentKeys[eventKey] += 1
389
+ else currentKeys[eventKey] = 1
390
+ })
391
+ this.options.map((option) => {
392
+ const key = option.key
393
+ const value = option.children.filter((child) => child.label === "Show all")[0].value
394
+ const total = option.total
395
+ // Remove events if all child items is checked
396
+ if (currentKeys[key] === total) {
397
+ event = event.filter((e) => e[0] !== option.key)
398
+ delete currentKeys[key]
399
+ }
400
+ // Add 'Show all' if facet type not exist in event
401
+ if (!(key in currentKeys)) event.unshift([key, value])
402
+ })
403
+ return event
220
404
  },
221
405
  // cascadeEvent: initiate searches based off cascader changes
222
406
  cascadeEvent: function (event) {
223
407
  if (event) {
224
408
  // Check for show all in selected cascade options
225
- event = this.showAllEventModifier(event);
409
+ event = this.showAllEventModifier(event)
410
+
411
+ event = this.showAllEventModifierForAutoCheckAll(event)
412
+ /**
413
+ * Move the new added event to the beginning
414
+ * Otherwise, cascader will show different expand item
415
+ */
416
+ if (this.__expandItem__) {
417
+ let position = 0
418
+ if (this.__expandItem__.length > 1) {
419
+ position = 1
420
+ }
421
+ const current = event.filter((e) => e[position] == this.__expandItem__[position]);
422
+ const rest = event.filter((e) => e[position] !== this.__expandItem__[position]);
423
+ event = [...current, ...rest]
424
+ }
226
425
 
227
- // Create results for the filter update
228
- let filterKeys = event.filter( selection => selection !== undefined).map( fs => ({
229
- facetPropPath: fs[0],
230
- facet: fs[1].split(">")[1],
231
- term: fs[1].split(">")[0],
232
- AND: fs[2] // for setting the boolean
233
- }))
426
+ // Create results for the filter update
427
+ let filterKeys = event
428
+ .filter((selection) => selection !== undefined)
429
+ .map((fs) => {
430
+ let { hString, bString } =
431
+ this.findHierarachyStringAndBooleanString(fs)
432
+ let { facet, facet2, term } =
433
+ this.getFacetsFromHierarchyString(hString)
434
+ return {
435
+ facetPropPath: fs[0],
436
+ facet: facet,
437
+ facet2: facet2,
438
+ term: term,
439
+ AND: bString, // for setting the boolean
440
+ }
441
+ })
234
442
 
235
443
  // Move results from arrays to object for use on scicrunch (note that we remove 'duplicate' as that is only needed for filter keys)
236
- let filters = event.filter( selection => selection !== undefined).map( fs => {
237
- let propPath = fs[0].includes('duplicate') ? fs[0].split('duplicate')[0] : fs[0]
238
- return {
239
- facetPropPath: propPath,
240
- facet: fs[1].split(">")[1],
241
- term: fs[1].split(">")[0],
242
- AND: fs[2] // for setting the boolean
243
- }
244
- })
444
+ let filters = event
445
+ .filter((selection) => selection !== undefined)
446
+ .map((fs) => {
447
+ let facetSubPropPath = undefined
448
+ let propPath = fs[0].includes('duplicate')
449
+ ? fs[0].split('duplicate')[0]
450
+ : fs[0]
451
+ let { hString, bString } =
452
+ this.findHierarachyStringAndBooleanString(fs)
453
+ let { facet, facet2, term } =
454
+ this.getFacetsFromHierarchyString(hString)
455
+ if (facet2) {
456
+ // We need to change the propPath if we are at the third level of the cascader
457
+ facet = facet2
458
+ facetSubPropPath = 'anatomy.organ.name'
459
+ }
460
+ return {
461
+ facetPropPath: propPath,
462
+ facet: facet,
463
+ term: term,
464
+ AND: bString, // for setting the boolean
465
+ facetSubPropPath: facetSubPropPath, // will be used for filters if we are at the third level of the cascader
466
+ }
467
+ })
245
468
 
246
469
  this.$emit('loading', true) // let sidebarcontent wait for the requests
247
470
 
248
- this.$emit("filterResults", filters); // emit filters for apps above sidebar
249
- this.setCascader(filterKeys); //update our cascader v-model if we modified the event
250
- this.makeCascadeLabelsClickable();
471
+ this.$emit('filterResults', filters) // emit filters for apps above sidebar
472
+ this.setCascader(filterKeys) //update our cascader v-model if we modified the event
473
+ this.cssMods() // update css for the cascader
251
474
  }
252
475
  },
476
+ //this fucntion is needed as we previously stored booleans in the array of event that
477
+ // are stored in the cascader
478
+ findHierarachyStringAndBooleanString(cascadeEventItem) {
479
+ let hString, bString
480
+ if (cascadeEventItem.length >= 3) {
481
+ if (cascadeEventItem[2] && cascadeEventItem[2].split('>').length > 2) {
482
+ hString = cascadeEventItem[2]
483
+ bString =
484
+ cascadeEventItem.length == 4 ? cascadeEventItem[3] : undefined
485
+ } else {
486
+ hString = cascadeEventItem[1]
487
+ bString = cascadeEventItem[2]
488
+ }
489
+ } else {
490
+ hString = cascadeEventItem[1]
491
+ bString = undefined
492
+ }
493
+ return { hString, bString }
494
+ },
495
+ // Splits the terms and facets from the string stored in the cascader
496
+ getFacetsFromHierarchyString(hierarchyString) {
497
+ let facet,
498
+ term,
499
+ facet2 = undefined
500
+ let fsSplit = hierarchyString.split('>')
501
+ if (fsSplit.length == 3) {
502
+ // if we are at the third level of the cascader
503
+ facet2 = fsSplit[2]
504
+ facet = fsSplit[1]
505
+ term = fsSplit[0]
506
+ } else {
507
+ facet = fsSplit[1]
508
+ term = fsSplit[0]
509
+ }
510
+ return { facet, facet2, term }
511
+ },
253
512
  // 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
254
513
  // *NOTE* Does NOT remove 'Show all' selections from showing in 'cascadeSelected'
255
514
  showAllEventModifier: function (event) {
256
515
  // check if show all is in the cascader checked option list
257
516
  let hasShowAll = event
258
- .map((ev) => (ev ? ev[1].toLowerCase().includes("show all") : false))
259
- .includes(true);
517
+ .map((ev) => (ev ? ev[1].toLowerCase().includes('show all') : false))
518
+ .includes(true)
260
519
  // remove all selected options below the show all if checked
261
520
  if (hasShowAll) {
262
- let modifiedEvent = [];
263
- let facetMaps = {};
521
+ let modifiedEvent = []
522
+ let facetMaps = {}
264
523
  //catagorised different facet items
265
524
  for (const i in event) {
266
- if (facetMaps[event[i][0]] === undefined) facetMaps[event[i][0]] = [];
267
- facetMaps[event[i][0]].push(event[i]);
525
+ if (facetMaps[event[i][0]] === undefined) facetMaps[event[i][0]] = []
526
+ facetMaps[event[i][0]].push(event[i])
268
527
  }
269
528
  // go through each facets
270
529
  for (const facet in facetMaps) {
271
- let showAll = undefined;
530
+ let showAll = undefined
272
531
  // Find the show all item if any
273
532
  for (let i = facetMaps[facet].length - 1; i >= 0; i--) {
274
- if (facetMaps[facet][i][1].toLowerCase().includes("show all")) {
533
+ if (facetMaps[facet][i][1].toLowerCase().includes('show all')) {
275
534
  //seperate the showAll item and the rest
276
- showAll = facetMaps[facet].splice(i, 1)[0];
277
- break;
535
+ showAll = facetMaps[facet].splice(i, 1)[0]
536
+ break
278
537
  }
279
538
  }
280
539
  if (showAll) {
@@ -282,14 +541,14 @@ export default {
282
541
  //Unset the show all if it was present previously
283
542
  //and there are other items
284
543
  if (facetMaps[facet].length > 0)
285
- modifiedEvent.push(...facetMaps[facet]);
286
- else modifiedEvent.push(showAll);
544
+ modifiedEvent.push(...facetMaps[facet])
545
+ else modifiedEvent.push(showAll)
287
546
  } else {
288
547
  //showAll is turned on
289
- modifiedEvent.push(showAll);
548
+ modifiedEvent.push(showAll)
290
549
  }
291
550
  } else {
292
- modifiedEvent.push(...facetMaps[facet]);
551
+ modifiedEvent.push(...facetMaps[facet])
293
552
  }
294
553
  }
295
554
  //Make sure the expanded item are sorted first.
@@ -297,41 +556,41 @@ export default {
297
556
  if (this.__expandItem__) {
298
557
  if (a[0] == this.__expandItem__) {
299
558
  if (b[0] == this.__expandItem__) {
300
- return 0;
559
+ return 0
301
560
  } else {
302
- return -1;
561
+ return -1
303
562
  }
304
563
  } else if (b[0] == this.__expandItem__) {
305
564
  if (a[0] == this.__expandItem__) {
306
- return 0;
565
+ return 0
307
566
  } else {
308
- return 1;
567
+ return 1
309
568
  }
310
569
  } else {
311
- return 0;
570
+ return 0
312
571
  }
313
- } else return 0;
314
- });
572
+ } else return 0
573
+ })
315
574
  }
316
- return event;
575
+ return event
317
576
  },
318
577
  cascadeExpandChange: function (event) {
319
578
  //work around as the expand item may change on modifying the cascade props
320
- this.__expandItem__ = event;
321
- this.makeCascadeLabelsClickable();
579
+ this.__expandItem__ = event
580
+ this.cssMods()
322
581
  },
323
582
  numberShownChanged: function (event) {
324
- this.$emit("numberPerPage", parseInt(event));
583
+ this.$emit('numberPerPage', parseInt(event))
325
584
  },
326
585
  updatePreviousShowAllChecked: function (options) {
327
586
  //Reset the states
328
587
  for (const facet in this.previousShowAllChecked) {
329
- this.previousShowAllChecked[facet] = false;
588
+ this.previousShowAllChecked[facet] = false
330
589
  }
331
590
  options.forEach((element) => {
332
- if (element[1].toLowerCase().includes("show all"))
333
- this.previousShowAllChecked[element[0]] = true;
334
- });
591
+ if (element[1].toLowerCase().includes('show all'))
592
+ this.previousShowAllChecked[element[0]] = true
593
+ })
335
594
  },
336
595
  // setCascader: Clears previous selections and takes in an array of facets to select: filterFacets
337
596
  // facets are in the form:
@@ -344,48 +603,78 @@ export default {
344
603
  setCascader: function (filterFacets) {
345
604
  //Do not set the value unless it is ready
346
605
  if (this.cascaderIsReady && filterFacets && filterFacets.length != 0) {
347
- this.cascadeSelected = filterFacets.map(e => {
348
- return [
606
+ this.cascadeSelected = filterFacets.map((e) => {
607
+ let filters = [
349
608
  e.facetPropPath,
350
609
  this.createCascaderItemValue(capitalise(e.term), e.facet),
351
610
  ]
352
- });
611
+ // Add the third level of the cascader if it exists
612
+ if (e.facet2)
613
+ filters.push(
614
+ this.createCascaderItemValue(
615
+ capitalise(e.term),
616
+ e.facet,
617
+ e.facet2
618
+ )
619
+ )
620
+ return filters
621
+ })
353
622
 
354
623
  // Unforttunately the cascader is very particular about it's v-model
355
624
  // to get around this we create a clone of it and use this clone for adding our boolean information
356
- this.cascadeSelectedWithBoolean= filterFacets.map(e => {
625
+ this.cascadeSelectedWithBoolean = filterFacets.map((e) => {
357
626
  return [
358
627
  e.facetPropPath,
359
628
  this.createCascaderItemValue(capitalise(e.term), e.facet),
360
- e.AND
629
+ e.AND,
361
630
  ]
362
- });
363
- this.updatePreviousShowAllChecked(this.cascadeSelected);
631
+ })
632
+ this.updatePreviousShowAllChecked(this.cascadeSelected)
364
633
  }
634
+
635
+ this.tagsChangedCallback(filterFacets);
365
636
  },
366
- addFilter: function (filter) {
637
+ addFilter: function (filterToAdd) {
367
638
  //Do not set the value unless it is ready
368
- if (this.cascaderIsReady && filter) {
369
- if (this.validateFilter(filter)) {
370
- this.cascadeSelected.filter(f=>f.term != filter.term)
371
- this.cascadeSelected.push([filter.facetPropPath, this.createCascaderItemValue(filter.term, filter.facet), filter.AND])
372
- this.cascadeSelectedWithBoolean.push([filter.facetPropPath, this.createCascaderItemValue(filter.term, filter.facet), filter.AND])
639
+ if (this.cascaderIsReady && filterToAdd) {
640
+ let filter = this.validateAndConvertFilterToHierarchical(filterToAdd)
641
+ if (filter) {
642
+ this.cascadeSelected.filter((f) => f.term != filter.term)
643
+ this.cascadeSelected.push([
644
+ filter.facetPropPath,
645
+ this.createCascaderItemValue(filter.term, filter.facet),
646
+ this.createCascaderItemValue(
647
+ filter.term,
648
+ filter.facet,
649
+ filter.facet2
650
+ ),
651
+ ])
652
+ this.cascadeSelectedWithBoolean.push([
653
+ filter.facetPropPath,
654
+ this.createCascaderItemValue(filter.term, filter.facet),
655
+ this.createCascaderItemValue(
656
+ filter.term,
657
+ filter.facet,
658
+ filter.facet2
659
+ ),
660
+ filter.AND,
661
+ ])
373
662
  // The 'AND' her is to set the boolean value when we search on the filters. It can be undefined without breaking anything
374
- return true;
663
+ return true
375
664
  }
376
665
  }
377
666
  },
378
- initiateSearch: function() {
667
+ initiateSearch: function () {
379
668
  this.cascadeEvent(this.cascadeSelectedWithBoolean)
380
669
  },
381
670
  // checkShowAllBoxes: Checks each 'Show all' cascade option by using the setCascader function
382
- checkShowAllBoxes: function(){
671
+ checkShowAllBoxes: function () {
383
672
  this.setCascader(
384
- this.options.map(option => {
673
+ this.options.map((option) => {
385
674
  return {
386
675
  facetPropPath: option.value,
387
676
  term: option.label,
388
- facet: 'Show all'
677
+ facet: 'Show all',
389
678
  }
390
679
  })
391
680
  )
@@ -393,70 +682,140 @@ export default {
393
682
  makeCascadeLabelsClickable: function () {
394
683
  // Next tick allows the cascader menu to change
395
684
  this.$nextTick(() => {
396
- this.$refs.cascader.$el
397
- .querySelectorAll(".el-cascader-node__label")
685
+ document
686
+ .querySelectorAll('.sidebar-cascader-popper .el-cascader-node__label')
398
687
  .forEach((el) => {
399
688
  // step through each cascade label
400
689
  el.onclick = function () {
401
- const checkbox = this.previousElementSibling;
690
+ const checkbox = this.previousElementSibling
402
691
  if (checkbox) {
403
- if (!checkbox.parentElement.attributes["aria-owns"]) {
692
+ if (!checkbox.parentElement.attributes['aria-owns']) {
404
693
  // check if we are at the lowest level of cascader
405
- this.previousElementSibling.click(); // Click the checkbox
694
+ this.previousElementSibling.click() // Click the checkbox
406
695
  }
407
696
  }
408
- };
409
- });
410
- });
697
+ }
698
+ })
699
+ })
411
700
  },
412
- /**
413
- * Validate ther filter term to make sure the term is correct
701
+
702
+ cssMods: function () {
703
+ this.makeCascadeLabelsClickable()
704
+ this.removeTopLevelCascaderCheckboxes()
705
+ },
706
+
707
+ removeTopLevelCascaderCheckboxes: function () {
708
+ // Next tick allows the cascader menu to change
709
+ this.$nextTick(() => {
710
+ let cascadePanels = document.querySelectorAll(
711
+ '.sidebar-cascader-popper .el-cascader-menu__list'
712
+ )
713
+ // Hide the checkboxes on the first level of the cascader
714
+ cascadePanels[0]
715
+ .querySelectorAll('.el-checkbox__input')
716
+ .forEach((el) => (el.style.display = 'none'))
717
+ })
718
+ },
719
+ /*
720
+ * Given a filter, the function below returns the filter in the format of the cascader, returns false if facet is not found
414
721
  */
415
- validateFilter: function(filter) {
722
+ validateAndConvertFilterToHierarchical: function (filter) {
416
723
  if (filter && filter.facet && filter.term) {
417
- const item = this.createCascaderItemValue(filter.term, filter.facet);
418
- const facet = this.options.find(element => element.value === filter.facetPropPath);
419
- if (facet) {
420
- const filter = facet.children.find(element => element.value === item);
421
- if (filter)
422
- return true;
724
+ if (filter.facet2) {
725
+ return filter // if it has a second term we will assume it is hierarchical and return it as is
726
+ } else {
727
+ for (const firstLayer of this.options) {
728
+ if (firstLayer.value === filter.facetPropPath) {
729
+ for (const secondLayer of firstLayer.children) {
730
+ if (secondLayer.label === filter.facet) {
731
+ // if we find a match on the second level, the filter will already be correct
732
+ return filter
733
+ } else {
734
+ if (secondLayer.children && secondLayer.children.length > 0) {
735
+ for (const thirdLayer of secondLayer.children) {
736
+ if (thirdLayer.label === filter.facet) {
737
+ // If we find a match on the third level, we need to switch facet1 to facet2
738
+ // and populate facet1 with its parents label.
739
+ filter.facet2 = thirdLayer.label
740
+ filter.facet = secondLayer.label
741
+ return filter
742
+ }
743
+ }
744
+ }
745
+ }
746
+ }
747
+ }
748
+ }
423
749
  }
424
750
  }
425
- return false;
751
+ return false
426
752
  },
427
- /**
428
- * Return a list of valid filers given a list of filters,
429
- */
430
- getValidatedFilters: function (filters) {
753
+ getHierarchicalValidatedFilters: function (filters) {
431
754
  if (filters) {
432
755
  if (this.cascaderIsReady) {
433
- const result = [];
434
- filters.forEach(filter => {
435
- if (this.validateFilter(filter)) {
436
- result.push(filter);
756
+ const result = []
757
+ filters.forEach((filter) => {
758
+ const validatedFilter =
759
+ this.validateAndConvertFilterToHierarchical(filter)
760
+ if (validatedFilter) {
761
+ result.push(validatedFilter)
437
762
  }
438
- });
439
- return result;
440
- } else return filters;
763
+ })
764
+ return result
765
+ } else return filters
441
766
  }
442
- return [];
767
+ return []
443
768
  },
444
769
  },
445
770
  mounted: function () {
446
- if (!this.alternateSearch) {
447
- this.algoliaClient = new AlgoliaClient(this.envVars.ALGOLIA_ID, this.envVars.ALGOLIA_KEY, this.envVars.PENNSIEVE_API_LOCATION);
448
- this.algoliaClient.initIndex(this.envVars.ALGOLIA_INDEX);
449
- }
450
- this.populateCascader();
771
+ this.algoliaClient = new AlgoliaClient(
772
+ this.envVars.ALGOLIA_ID,
773
+ this.envVars.ALGOLIA_KEY,
774
+ this.envVars.PENNSIEVE_API_LOCATION
775
+ )
776
+ this.algoliaClient.initIndex(this.envVars.ALGOLIA_INDEX)
777
+ this.populateCascader().then(() => {
778
+ this.cascaderIsReady = true
779
+ this.checkShowAllBoxes()
780
+ this.setCascader(this.entry.filterFacets)
781
+ this.cssMods()
782
+ this.$emit('cascaderReady')
783
+ })
451
784
  },
452
- };
785
+ }
453
786
  </script>
454
787
 
455
- <!-- Add "scoped" attribute to limit CSS to this component only -->
456
- <style scoped lang="scss">
457
- @import "~element-ui/packages/theme-chalk/src/option";
458
- @import "~element-ui/packages/theme-chalk/src/popover";
459
- @import "~element-ui/packages/theme-chalk/src/select";
788
+ <style lang="scss" scoped>
789
+
790
+ .cascader-tag {
791
+ position: absolute;
792
+ top: 110px;
793
+ left: 50px;
794
+ z-index: 1;
795
+ display: flex;
796
+ gap: 4px;
797
+ }
798
+
799
+ .el-tags-container {
800
+ display: flex;
801
+ flex-wrap: wrap;
802
+ gap: 4px;
803
+ }
804
+
805
+ .el-tag {
806
+ .cascader-tag &,
807
+ .el-tags-container & {
808
+ font-family: 'Asap', sans-serif;
809
+ font-size: 12px;
810
+ color: #303133 !important;
811
+ background-color: #fff;
812
+ border-color: #dcdfe6 !important;
813
+ }
814
+ }
815
+
816
+ :deep(.el-cascader__tags) {
817
+ display: none;
818
+ }
460
819
 
461
820
  .filter-default-value {
462
821
  pointer-events: none;
@@ -501,21 +860,16 @@ export default {
501
860
  padding-bottom: 6px;
502
861
  }
503
862
 
504
- .cascader ::v-deep .el-cascder-panel {
505
- max-height: 500px;
506
- }
507
-
508
- .cascader::v-deep .el-scrollbar__wrap {
509
- overflow-x: hidden;
510
- margin-bottom: 2px !important;
511
- }
512
-
513
- .cascader ::v-deep li[aria-owns*="cascader"] > .el-checkbox {
514
- display: none;
863
+ .dataset-shown {
864
+ display: flex;
865
+ flex-direction: row;
866
+ float: right;
867
+ padding-bottom: 6px;
868
+ gap: 8px;
515
869
  }
516
870
 
517
871
  .dataset-results-feedback {
518
- float: right;
872
+ white-space:nowrap;
519
873
  text-align: right;
520
874
  color: rgb(48, 49, 51);
521
875
  font-family: Asap;
@@ -528,82 +882,113 @@ export default {
528
882
  position: relative;
529
883
  float: left;
530
884
  padding-right: 15px;
531
- padding-bottom: 12px;
532
885
  }
533
886
 
534
- .number-shown-select {
535
- float: right;
536
- }
537
-
538
- .number-shown-select ::v-deep .el-input__inner {
887
+ .number-shown-select :deep(.el-select__wrapper) {
539
888
  width: 68px;
540
889
  height: 40px;
541
890
  color: rgb(48, 49, 51);
542
891
  }
543
892
 
544
- .search-filters ::v-deep .el-cascader-node.is-active {
545
- color: $app-primary-color;
546
- }
547
-
548
- .search-filters ::v-deep .el-cascader-node.in-active-path {
549
- color: $app-primary-color;
550
- }
551
-
552
- .search-filters ::v-deep .el-checkbox__input.is-checked > .el-checkbox__inner {
553
- background-color: $app-primary-color;
554
- border-color: $app-primary-color;
555
- }
556
-
557
- .cascader ::v-deep .el-cascader-menu:nth-child(2) .el-cascader-node:first-child {
558
- border-bottom: 1px solid #e4e7ed;
559
- }
560
-
561
- .cascader ::v-deep .el-cascader-node__label {
562
- text-align: left;
893
+ .el-select-dropdown__item.is-selected {
894
+ color: #8300BF;
563
895
  }
564
896
 
565
- .filters ::v-deep .el-popover {
566
- background: #f3ecf6 !important;
567
- border: 1px solid $app-primary-color;
568
- border-radius: 4px;
569
- color: #303133 !important;
570
- font-size: 12px;
571
- line-height: 18px;
897
+ .filters :deep(.el-popover) {
898
+ background: #f3ecf6 !important;
899
+ border: 1px solid $app-primary-color;
900
+ border-radius: 4px;
901
+ color: #303133 !important;
902
+ font-size: 12px;
903
+ line-height: 18px;
572
904
  }
573
905
 
574
- .filters ::v-deep .el-popover[x-placement^="top"] .popper__arrow {
906
+ .filters :deep(.el-popover[x-placement^='top'] .popper__arrow) {
575
907
  border-top-color: $app-primary-color;
576
908
  border-bottom-width: 0;
577
909
  }
578
- .filters ::v-deep .el-popover[x-placement^="top"] .popper__arrow::after {
910
+ .filters :deep(.el-popover[x-placement^='top'] .popper__arrow::after) {
579
911
  border-top-color: #f3ecf6;
580
912
  border-bottom-width: 0;
581
913
  }
582
914
 
583
- .filters ::v-deep .el-popover[x-placement^="bottom"] .popper__arrow {
915
+ .filters :deep(.el-popover[x-placement^='bottom'] .popper__arrow) {
584
916
  border-top-width: 0;
585
917
  border-bottom-color: $app-primary-color;
586
918
  }
587
- .filters ::v-deep .el-popover[x-placement^="bottom"] .popper__arrow::after {
919
+ .filters :deep(.el-popover[x-placement^='bottom'] .popper__arrow::after) {
588
920
  border-top-width: 0;
589
921
  border-bottom-color: #f3ecf6;
590
922
  }
591
923
 
592
- .filters ::v-deep .el-popover[x-placement^="right"] .popper__arrow {
924
+ .filters :deep(.el-popover[x-placement^='right'] .popper__arrow) {
593
925
  border-right-color: $app-primary-color;
594
926
  border-left-width: 0;
595
927
  }
596
- .filters ::v-deep .el-popover[x-placement^="right"] .popper__arrow::after {
928
+ .filters :deep(.el-popover[x-placement^='right'] .popper__arrow::after) {
597
929
  border-right-color: #f3ecf6;
598
930
  border-left-width: 0;
599
931
  }
600
932
 
601
- .filters ::v-deep .el-popover[x-placement^="left"] .popper__arrow {
933
+ .filters :deep(.el-popover[x-placement^='left'] .popper__arrow) {
602
934
  border-right-width: 0;
603
935
  border-left-color: $app-primary-color;
604
936
  }
605
- .filters ::v-deep .el-popover[x-placement^="left"] .popper__arrow::after {
937
+ .filters :deep(.el-popover[x-placement^='left'] .popper__arrow::after) {
606
938
  border-right-width: 0;
607
939
  border-left-color: #f3ecf6;
608
940
  }
609
941
  </style>
942
+
943
+ <style lang="scss">
944
+ .sidebar-cascader-popper {
945
+ font-family: Asap;
946
+ font-size: 14px;
947
+ font-weight: 500;
948
+ font-stretch: normal;
949
+ font-style: normal;
950
+ line-height: normal;
951
+ letter-spacing: normal;
952
+ color: #292b66;
953
+ text-align: center;
954
+ padding-bottom: 6px;
955
+ }
956
+
957
+ .sidebar-cascader-popper .el-cascader-node.is-active {
958
+ color: $app-primary-color;
959
+ }
960
+
961
+ .sidebar-cascader-popper .el-cascader-node.in-active-path {
962
+ color: $app-primary-color;
963
+ }
964
+
965
+ .sidebar-cascader-popper .el-checkbox__input.is-checked > .el-checkbox__inner {
966
+ background-color: $app-primary-color;
967
+ border-color: $app-primary-color;
968
+ }
969
+
970
+ .sidebar-cascader-popper
971
+ .el-cascader-menu:nth-child(2)
972
+ .el-cascader-node:first-child {
973
+ border-bottom: 1px solid #e4e7ed;
974
+ }
975
+
976
+ .sidebar-cascader-popper .el-cascader-node__label {
977
+ text-align: left;
978
+ }
979
+
980
+ .sidebar-cascader-popper .el-cascder-panel {
981
+ max-height: 500px;
982
+ }
983
+
984
+ .sidebar-cascader-popper .el-scrollbar__wrap {
985
+ overflow-x: hidden;
986
+ margin-bottom: 2px !important;
987
+ }
988
+
989
+ .sidebar-cascader-popper .el-checkbox__input.is-checked .el-checkbox__inner,
990
+ .el-checkbox__input.is-indeterminate .el-checkbox__inner {
991
+ background-color: $app-primary-color;
992
+ border-color: $app-primary-color;
993
+ }
994
+ </style>