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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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 +21143 -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 +687 -296
  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,377 @@ 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
- cascadeEvent: function (event) {
406
+ cascadeEvent: function (eventIn) {
407
+ let event = [...eventIn]
223
408
  if (event) {
224
409
  // Check for show all in selected cascade options
225
- event = this.showAllEventModifier(event);
226
-
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
- }))
234
410
 
235
- // 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
411
+ event = this.showAllEventModifier(event)
412
+
413
+ event = this.showAllEventModifierForAutoCheckAll(event)
414
+ /**
415
+ * Move the new added event to the beginning
416
+ * Otherwise, cascader will show different expand item
417
+ */
418
+ if (this.__expandItem__) {
419
+ let position = 0
420
+ if (this.__expandItem__.length > 1) {
421
+ position = 1
243
422
  }
244
- })
423
+ const current = event.filter((e) => e[position] == this.__expandItem__[position]);
424
+ const rest = event.filter((e) => e[position] !== this.__expandItem__[position]);
425
+ event = [...current, ...rest]
426
+ }
427
+ // Create results for the filter update
428
+ let filterKeys = event
429
+ .filter((selection) => selection !== undefined)
430
+ .map((fs) => {
431
+ let { hString, bString } =
432
+ this.findHierarachyStringAndBooleanString(fs)
433
+ let { facet, facet2, term } =
434
+ this.getFacetsFromHierarchyString(hString)
435
+ return {
436
+ facetPropPath: fs[0],
437
+ facet: facet,
438
+ facet2: facet2,
439
+ term: term,
440
+ AND: bString, // for setting the boolean
441
+ }
442
+ })
245
443
 
444
+ // Move results from arrays to object for use on scicrunch (note that we remove 'duplicate' as that is only needed for filter keys)
445
+ let filters = event
446
+ .filter((selection) => selection !== undefined)
447
+ .map((fs) => {
448
+ let facetSubPropPath = undefined
449
+ let propPath = fs[0].includes('duplicate')
450
+ ? fs[0].split('duplicate')[0]
451
+ : fs[0]
452
+ let { hString, bString } =
453
+ this.findHierarachyStringAndBooleanString(fs)
454
+ let { facet, facet2, term } =
455
+ this.getFacetsFromHierarchyString(hString)
456
+ if (facet2) {
457
+ // We need to change the propPath if we are at the third level of the cascader
458
+ facet = facet2
459
+ facetSubPropPath = 'anatomy.organ.name'
460
+ }
461
+ return {
462
+ facetPropPath: propPath,
463
+ facet: facet,
464
+ term: term,
465
+ AND: bString, // for setting the boolean
466
+ facetSubPropPath: facetSubPropPath, // will be used for filters if we are at the third level of the cascader
467
+ }
468
+ })
469
+
246
470
  this.$emit('loading', true) // let sidebarcontent wait for the requests
247
-
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] &&
482
+ (typeof cascadeEventItem[2] === 'string' ||
483
+ cascadeEventItem[2] instanceof String) &&
484
+ cascadeEventItem[2].split('>').length > 2) {
485
+ hString = cascadeEventItem[2]
486
+ bString = cascadeEventItem.length == 4 ? cascadeEventItem[3] : undefined
487
+ } else {
488
+ hString = cascadeEventItem[1]
489
+ bString = cascadeEventItem[2]
490
+ }
491
+ } else {
492
+ hString = cascadeEventItem[1]
493
+ bString = undefined
494
+ }
495
+ return { hString, bString }
496
+ },
497
+ // Splits the terms and facets from the string stored in the cascader
498
+ getFacetsFromHierarchyString(hierarchyString) {
499
+ let facet,
500
+ term,
501
+ facet2 = undefined
502
+ let fsSplit = hierarchyString.split('>')
503
+ if (fsSplit.length == 3) {
504
+ // if we are at the third level of the cascader
505
+ facet2 = fsSplit[2]
506
+ facet = fsSplit[1]
507
+ term = fsSplit[0]
508
+ } else {
509
+ facet = fsSplit[1]
510
+ term = fsSplit[0]
511
+ }
512
+ return { facet, facet2, term }
513
+ },
253
514
  // 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
515
  // *NOTE* Does NOT remove 'Show all' selections from showing in 'cascadeSelected'
255
516
  showAllEventModifier: function (event) {
256
517
  // check if show all is in the cascader checked option list
257
518
  let hasShowAll = event
258
- .map((ev) => (ev ? ev[1].toLowerCase().includes("show all") : false))
259
- .includes(true);
519
+ .map((ev) => (ev ? ev[1].toLowerCase().includes('show all') : false))
520
+ .includes(true)
260
521
  // remove all selected options below the show all if checked
261
522
  if (hasShowAll) {
262
- let modifiedEvent = [];
263
- let facetMaps = {};
523
+ let modifiedEvent = []
524
+ let facetMaps = {}
264
525
  //catagorised different facet items
265
526
  for (const i in event) {
266
- if (facetMaps[event[i][0]] === undefined) facetMaps[event[i][0]] = [];
267
- facetMaps[event[i][0]].push(event[i]);
527
+ if (facetMaps[event[i][0]] === undefined) facetMaps[event[i][0]] = []
528
+ facetMaps[event[i][0]].push(event[i])
268
529
  }
269
530
  // go through each facets
270
531
  for (const facet in facetMaps) {
271
- let showAll = undefined;
532
+ let showAll = undefined
272
533
  // Find the show all item if any
273
534
  for (let i = facetMaps[facet].length - 1; i >= 0; i--) {
274
- if (facetMaps[facet][i][1].toLowerCase().includes("show all")) {
535
+ if (facetMaps[facet][i][1].toLowerCase().includes('show all')) {
275
536
  //seperate the showAll item and the rest
276
- showAll = facetMaps[facet].splice(i, 1)[0];
277
- break;
537
+ showAll = facetMaps[facet].splice(i, 1)[0]
538
+ break
278
539
  }
279
540
  }
280
541
  if (showAll) {
@@ -282,14 +543,14 @@ export default {
282
543
  //Unset the show all if it was present previously
283
544
  //and there are other items
284
545
  if (facetMaps[facet].length > 0)
285
- modifiedEvent.push(...facetMaps[facet]);
286
- else modifiedEvent.push(showAll);
546
+ modifiedEvent.push(...facetMaps[facet])
547
+ else modifiedEvent.push(showAll)
287
548
  } else {
288
549
  //showAll is turned on
289
- modifiedEvent.push(showAll);
550
+ modifiedEvent.push(showAll)
290
551
  }
291
552
  } else {
292
- modifiedEvent.push(...facetMaps[facet]);
553
+ modifiedEvent.push(...facetMaps[facet])
293
554
  }
294
555
  }
295
556
  //Make sure the expanded item are sorted first.
@@ -297,41 +558,41 @@ export default {
297
558
  if (this.__expandItem__) {
298
559
  if (a[0] == this.__expandItem__) {
299
560
  if (b[0] == this.__expandItem__) {
300
- return 0;
561
+ return 0
301
562
  } else {
302
- return -1;
563
+ return -1
303
564
  }
304
565
  } else if (b[0] == this.__expandItem__) {
305
566
  if (a[0] == this.__expandItem__) {
306
- return 0;
567
+ return 0
307
568
  } else {
308
- return 1;
569
+ return 1
309
570
  }
310
571
  } else {
311
- return 0;
572
+ return 0
312
573
  }
313
- } else return 0;
314
- });
574
+ } else return 0
575
+ })
315
576
  }
316
- return event;
577
+ return event
317
578
  },
318
579
  cascadeExpandChange: function (event) {
319
580
  //work around as the expand item may change on modifying the cascade props
320
- this.__expandItem__ = event;
321
- this.makeCascadeLabelsClickable();
581
+ this.__expandItem__ = event
582
+ this.cssMods()
322
583
  },
323
584
  numberShownChanged: function (event) {
324
- this.$emit("numberPerPage", parseInt(event));
585
+ this.$emit('numberPerPage', parseInt(event))
325
586
  },
326
587
  updatePreviousShowAllChecked: function (options) {
327
588
  //Reset the states
328
589
  for (const facet in this.previousShowAllChecked) {
329
- this.previousShowAllChecked[facet] = false;
590
+ this.previousShowAllChecked[facet] = false
330
591
  }
331
592
  options.forEach((element) => {
332
- if (element[1].toLowerCase().includes("show all"))
333
- this.previousShowAllChecked[element[0]] = true;
334
- });
593
+ if (element[1].toLowerCase().includes('show all'))
594
+ this.previousShowAllChecked[element[0]] = true
595
+ })
335
596
  },
336
597
  // setCascader: Clears previous selections and takes in an array of facets to select: filterFacets
337
598
  // facets are in the form:
@@ -344,48 +605,82 @@ export default {
344
605
  setCascader: function (filterFacets) {
345
606
  //Do not set the value unless it is ready
346
607
  if (this.cascaderIsReady && filterFacets && filterFacets.length != 0) {
347
- this.cascadeSelected = filterFacets.map(e => {
348
- return [
608
+ //An inner function only used by this function
609
+ const createFilter = (e) => {
610
+ let filters = [
349
611
  e.facetPropPath,
350
612
  this.createCascaderItemValue(capitalise(e.term), e.facet),
351
613
  ]
352
- });
614
+ // Add the third level of the cascader if it exists
615
+ if (e.facet2) {
616
+ filters.push(
617
+ this.createCascaderItemValue(
618
+ capitalise(e.term),
619
+ e.facet,
620
+ e.facet2
621
+ )
622
+ )
623
+ }
624
+ return filters;
625
+ }
353
626
 
627
+ this.cascadeSelected = filterFacets.map((e) => {
628
+ let filters = createFilter(e)
629
+ return filters
630
+ })
631
+
354
632
  // Unforttunately the cascader is very particular about it's v-model
355
633
  // 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 => {
357
- return [
358
- e.facetPropPath,
359
- this.createCascaderItemValue(capitalise(e.term), e.facet),
360
- e.AND
361
- ]
362
- });
363
- this.updatePreviousShowAllChecked(this.cascadeSelected);
634
+ this.cascadeSelectedWithBoolean = filterFacets.map((e) => {
635
+ let filters = createFilter(e)
636
+ filters.push(e.AND)
637
+ return filters
638
+ })
639
+ this.updatePreviousShowAllChecked(this.cascadeSelected)
364
640
  }
641
+ this.tagsChangedCallback(filterFacets);
365
642
  },
366
- addFilter: function (filter) {
643
+ addFilter: function (filterToAdd) {
367
644
  //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])
645
+ if (this.cascaderIsReady && filterToAdd) {
646
+ let filter = this.validateAndConvertFilterToHierarchical(filterToAdd)
647
+ if (filter) {
648
+ this.cascadeSelected.filter((f) => f.term != filter.term)
649
+ this.cascadeSelected.push([
650
+ filter.facetPropPath,
651
+ this.createCascaderItemValue(filter.term, filter.facet),
652
+ this.createCascaderItemValue(
653
+ filter.term,
654
+ filter.facet,
655
+ filter.facet2
656
+ ),
657
+ ])
658
+ this.cascadeSelectedWithBoolean.push([
659
+ filter.facetPropPath,
660
+ this.createCascaderItemValue(filter.term, filter.facet),
661
+ this.createCascaderItemValue(
662
+ filter.term,
663
+ filter.facet,
664
+ filter.facet2
665
+ ),
666
+ filter.AND,
667
+ ])
373
668
  // 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;
669
+ return true
375
670
  }
376
671
  }
377
672
  },
378
- initiateSearch: function() {
673
+ initiateSearch: function () {
379
674
  this.cascadeEvent(this.cascadeSelectedWithBoolean)
380
675
  },
381
676
  // checkShowAllBoxes: Checks each 'Show all' cascade option by using the setCascader function
382
- checkShowAllBoxes: function(){
677
+ checkShowAllBoxes: function () {
383
678
  this.setCascader(
384
- this.options.map(option => {
679
+ this.options.map((option) => {
385
680
  return {
386
681
  facetPropPath: option.value,
387
682
  term: option.label,
388
- facet: 'Show all'
683
+ facet: 'Show all',
389
684
  }
390
685
  })
391
686
  )
@@ -393,70 +688,140 @@ export default {
393
688
  makeCascadeLabelsClickable: function () {
394
689
  // Next tick allows the cascader menu to change
395
690
  this.$nextTick(() => {
396
- this.$refs.cascader.$el
397
- .querySelectorAll(".el-cascader-node__label")
691
+ document
692
+ .querySelectorAll('.sidebar-cascader-popper .el-cascader-node__label')
398
693
  .forEach((el) => {
399
694
  // step through each cascade label
400
695
  el.onclick = function () {
401
- const checkbox = this.previousElementSibling;
696
+ const checkbox = this.previousElementSibling
402
697
  if (checkbox) {
403
- if (!checkbox.parentElement.attributes["aria-owns"]) {
698
+ if (!checkbox.parentElement.attributes['aria-owns']) {
404
699
  // check if we are at the lowest level of cascader
405
- this.previousElementSibling.click(); // Click the checkbox
700
+ this.previousElementSibling.click() // Click the checkbox
406
701
  }
407
702
  }
408
- };
409
- });
410
- });
703
+ }
704
+ })
705
+ })
411
706
  },
412
- /**
413
- * Validate ther filter term to make sure the term is correct
707
+
708
+ cssMods: function () {
709
+ this.makeCascadeLabelsClickable()
710
+ this.removeTopLevelCascaderCheckboxes()
711
+ },
712
+
713
+ removeTopLevelCascaderCheckboxes: function () {
714
+ // Next tick allows the cascader menu to change
715
+ this.$nextTick(() => {
716
+ let cascadePanels = document.querySelectorAll(
717
+ '.sidebar-cascader-popper .el-cascader-menu__list'
718
+ )
719
+ // Hide the checkboxes on the first level of the cascader
720
+ cascadePanels[0]
721
+ .querySelectorAll('.el-checkbox__input')
722
+ .forEach((el) => (el.style.display = 'none'))
723
+ })
724
+ },
725
+ /*
726
+ * Given a filter, the function below returns the filter in the format of the cascader, returns false if facet is not found
414
727
  */
415
- validateFilter: function(filter) {
728
+ validateAndConvertFilterToHierarchical: function (filter) {
416
729
  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;
730
+ if (filter.facet2) {
731
+ return filter // if it has a second term we will assume it is hierarchical and return it as is
732
+ } else {
733
+ for (const firstLayer of this.options) {
734
+ if (firstLayer.value === filter.facetPropPath) {
735
+ for (const secondLayer of firstLayer.children) {
736
+ if (secondLayer.label === filter.facet) {
737
+ // if we find a match on the second level, the filter will already be correct
738
+ return filter
739
+ } else {
740
+ if (secondLayer.children && secondLayer.children.length > 0) {
741
+ for (const thirdLayer of secondLayer.children) {
742
+ if (thirdLayer.label === filter.facet) {
743
+ // If we find a match on the third level, we need to switch facet1 to facet2
744
+ // and populate facet1 with its parents label.
745
+ filter.facet2 = thirdLayer.label
746
+ filter.facet = secondLayer.label
747
+ return filter
748
+ }
749
+ }
750
+ }
751
+ }
752
+ }
753
+ }
754
+ }
423
755
  }
424
756
  }
425
- return false;
757
+ return false
426
758
  },
427
- /**
428
- * Return a list of valid filers given a list of filters,
429
- */
430
- getValidatedFilters: function (filters) {
759
+ getHierarchicalValidatedFilters: function (filters) {
431
760
  if (filters) {
432
761
  if (this.cascaderIsReady) {
433
- const result = [];
434
- filters.forEach(filter => {
435
- if (this.validateFilter(filter)) {
436
- result.push(filter);
762
+ const result = []
763
+ filters.forEach((filter) => {
764
+ const validatedFilter =
765
+ this.validateAndConvertFilterToHierarchical(filter)
766
+ if (validatedFilter) {
767
+ result.push(validatedFilter)
437
768
  }
438
- });
439
- return result;
440
- } else return filters;
769
+ })
770
+ return result
771
+ } else return filters
441
772
  }
442
- return [];
773
+ return []
443
774
  },
444
775
  },
445
776
  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();
777
+ this.algoliaClient = new AlgoliaClient(
778
+ this.envVars.ALGOLIA_ID,
779
+ this.envVars.ALGOLIA_KEY,
780
+ this.envVars.PENNSIEVE_API_LOCATION
781
+ )
782
+ this.algoliaClient.initIndex(this.envVars.ALGOLIA_INDEX)
783
+ this.populateCascader().then(() => {
784
+ this.cascaderIsReady = true
785
+ this.checkShowAllBoxes()
786
+ this.setCascader(this.entry.filterFacets)
787
+ this.cssMods()
788
+ this.$emit('cascaderReady')
789
+ })
451
790
  },
452
- };
791
+ }
453
792
  </script>
454
793
 
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";
794
+ <style lang="scss" scoped>
795
+
796
+ .cascader-tag {
797
+ position: absolute;
798
+ top: 110px;
799
+ left: 50px;
800
+ z-index: 1;
801
+ display: flex;
802
+ gap: 4px;
803
+ }
804
+
805
+ .el-tags-container {
806
+ display: flex;
807
+ flex-wrap: wrap;
808
+ gap: 4px;
809
+ }
810
+
811
+ .el-tag {
812
+ .cascader-tag &,
813
+ .el-tags-container & {
814
+ font-family: 'Asap', sans-serif;
815
+ font-size: 12px;
816
+ color: #303133 !important;
817
+ background-color: #fff;
818
+ border-color: #dcdfe6 !important;
819
+ }
820
+ }
821
+
822
+ :deep(.el-cascader__tags) {
823
+ display: none;
824
+ }
460
825
 
461
826
  .filter-default-value {
462
827
  pointer-events: none;
@@ -501,21 +866,16 @@ export default {
501
866
  padding-bottom: 6px;
502
867
  }
503
868
 
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;
869
+ .dataset-shown {
870
+ display: flex;
871
+ flex-direction: row;
872
+ float: right;
873
+ padding-bottom: 6px;
874
+ gap: 8px;
515
875
  }
516
876
 
517
877
  .dataset-results-feedback {
518
- float: right;
878
+ white-space:nowrap;
519
879
  text-align: right;
520
880
  color: rgb(48, 49, 51);
521
881
  font-family: Asap;
@@ -528,82 +888,113 @@ export default {
528
888
  position: relative;
529
889
  float: left;
530
890
  padding-right: 15px;
531
- padding-bottom: 12px;
532
- }
533
-
534
- .number-shown-select {
535
- float: right;
536
891
  }
537
892
 
538
- .number-shown-select ::v-deep .el-input__inner {
893
+ .number-shown-select :deep(.el-select__wrapper) {
539
894
  width: 68px;
540
895
  height: 40px;
541
896
  color: rgb(48, 49, 51);
542
897
  }
543
898
 
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;
899
+ .el-select-dropdown__item.is-selected {
900
+ color: #8300BF;
563
901
  }
564
902
 
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;
903
+ .filters :deep(.el-popover) {
904
+ background: #f3ecf6 !important;
905
+ border: 1px solid $app-primary-color;
906
+ border-radius: 4px;
907
+ color: #303133 !important;
908
+ font-size: 12px;
909
+ line-height: 18px;
572
910
  }
573
911
 
574
- .filters ::v-deep .el-popover[x-placement^="top"] .popper__arrow {
912
+ .filters :deep(.el-popover[x-placement^='top'] .popper__arrow) {
575
913
  border-top-color: $app-primary-color;
576
914
  border-bottom-width: 0;
577
915
  }
578
- .filters ::v-deep .el-popover[x-placement^="top"] .popper__arrow::after {
916
+ .filters :deep(.el-popover[x-placement^='top'] .popper__arrow::after) {
579
917
  border-top-color: #f3ecf6;
580
918
  border-bottom-width: 0;
581
919
  }
582
920
 
583
- .filters ::v-deep .el-popover[x-placement^="bottom"] .popper__arrow {
921
+ .filters :deep(.el-popover[x-placement^='bottom'] .popper__arrow) {
584
922
  border-top-width: 0;
585
923
  border-bottom-color: $app-primary-color;
586
924
  }
587
- .filters ::v-deep .el-popover[x-placement^="bottom"] .popper__arrow::after {
925
+ .filters :deep(.el-popover[x-placement^='bottom'] .popper__arrow::after) {
588
926
  border-top-width: 0;
589
927
  border-bottom-color: #f3ecf6;
590
928
  }
591
929
 
592
- .filters ::v-deep .el-popover[x-placement^="right"] .popper__arrow {
930
+ .filters :deep(.el-popover[x-placement^='right'] .popper__arrow) {
593
931
  border-right-color: $app-primary-color;
594
932
  border-left-width: 0;
595
933
  }
596
- .filters ::v-deep .el-popover[x-placement^="right"] .popper__arrow::after {
934
+ .filters :deep(.el-popover[x-placement^='right'] .popper__arrow::after) {
597
935
  border-right-color: #f3ecf6;
598
936
  border-left-width: 0;
599
937
  }
600
938
 
601
- .filters ::v-deep .el-popover[x-placement^="left"] .popper__arrow {
939
+ .filters :deep(.el-popover[x-placement^='left'] .popper__arrow) {
602
940
  border-right-width: 0;
603
941
  border-left-color: $app-primary-color;
604
942
  }
605
- .filters ::v-deep .el-popover[x-placement^="left"] .popper__arrow::after {
943
+ .filters :deep(.el-popover[x-placement^='left'] .popper__arrow::after) {
606
944
  border-right-width: 0;
607
945
  border-left-color: #f3ecf6;
608
946
  }
609
947
  </style>
948
+
949
+ <style lang="scss">
950
+ .sidebar-cascader-popper {
951
+ font-family: Asap;
952
+ font-size: 14px;
953
+ font-weight: 500;
954
+ font-stretch: normal;
955
+ font-style: normal;
956
+ line-height: normal;
957
+ letter-spacing: normal;
958
+ color: #292b66;
959
+ text-align: center;
960
+ padding-bottom: 6px;
961
+ }
962
+
963
+ .sidebar-cascader-popper .el-cascader-node.is-active {
964
+ color: $app-primary-color;
965
+ }
966
+
967
+ .sidebar-cascader-popper .el-cascader-node.in-active-path {
968
+ color: $app-primary-color;
969
+ }
970
+
971
+ .sidebar-cascader-popper .el-checkbox__input.is-checked > .el-checkbox__inner {
972
+ background-color: $app-primary-color;
973
+ border-color: $app-primary-color;
974
+ }
975
+
976
+ .sidebar-cascader-popper
977
+ .el-cascader-menu:nth-child(2)
978
+ .el-cascader-node:first-child {
979
+ border-bottom: 1px solid #e4e7ed;
980
+ }
981
+
982
+ .sidebar-cascader-popper .el-cascader-node__label {
983
+ text-align: left;
984
+ }
985
+
986
+ .sidebar-cascader-popper .el-cascder-panel {
987
+ max-height: 500px;
988
+ }
989
+
990
+ .sidebar-cascader-popper .el-scrollbar__wrap {
991
+ overflow-x: hidden;
992
+ margin-bottom: 2px !important;
993
+ }
994
+
995
+ .sidebar-cascader-popper .el-checkbox__input.is-checked .el-checkbox__inner,
996
+ .el-checkbox__input.is-indeterminate .el-checkbox__inner {
997
+ background-color: $app-primary-color;
998
+ border-color: $app-primary-color;
999
+ }
1000
+ </style>