@abi-software/map-side-bar 2.2.0 → 2.2.1-alpha-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.
- package/.eslintrc.js +12 -12
- package/.postcssrc.json +5 -5
- package/LICENSE +201 -201
- package/README.md +168 -168
- package/dist/map-side-bar.js +6982 -7170
- package/dist/map-side-bar.umd.cjs +50 -103
- package/dist/style.css +1 -1
- package/package.json +72 -72
- package/src/App.vue +233 -233
- package/src/algolia/algolia.js +242 -211
- package/src/algolia/utils.js +101 -101
- package/src/assets/_variables.scss +43 -43
- package/src/assets/styles.scss +6 -6
- package/src/components/BadgesGroup.vue +124 -124
- package/src/components/DatasetCard.vue +356 -356
- package/src/components/EventBus.js +3 -3
- package/src/components/ImageGallery.vue +542 -542
- package/src/components/SearchFilters.vue +1000 -1000
- package/src/components/SearchHistory.vue +175 -175
- package/src/components/SideBar.vue +347 -338
- package/src/components/SidebarContent.vue +576 -576
- package/src/components/Tabs.vue +78 -78
- package/src/components/index.js +8 -8
- package/src/components/species-map.js +8 -8
- package/src/main.js +9 -9
- package/src/mixins/S3Bucket.vue +37 -37
- package/static.json +6 -6
- package/vite.config.js +55 -55
- package/vuese-generator.js +65 -65
|
@@ -1,1000 +1,1000 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div class="filters">
|
|
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>
|
|
46
|
-
<transition name="el-zoom-in-top">
|
|
47
|
-
<span v-show="showFilters" v-loading="!cascaderIsReady" class="search-filters transition-box">
|
|
48
|
-
<el-cascader
|
|
49
|
-
class="cascader"
|
|
50
|
-
ref="cascader"
|
|
51
|
-
v-model="cascadeSelected"
|
|
52
|
-
size="large"
|
|
53
|
-
placeholder=" "
|
|
54
|
-
:collapse-tags="true"
|
|
55
|
-
collapse-tags-tooltip
|
|
56
|
-
:options="options"
|
|
57
|
-
:props="props"
|
|
58
|
-
@change="cascadeEvent($event)"
|
|
59
|
-
@expand-change="cascadeExpandChange"
|
|
60
|
-
:show-all-levels="true"
|
|
61
|
-
popper-class="sidebar-cascader-popper"
|
|
62
|
-
/>
|
|
63
|
-
<div v-if="showFiltersText" class="filter-default-value">Filters</div>
|
|
64
|
-
<el-popover
|
|
65
|
-
title="How do filters work?"
|
|
66
|
-
width="250"
|
|
67
|
-
trigger="hover"
|
|
68
|
-
:append-to-body="false"
|
|
69
|
-
popper-class="popover"
|
|
70
|
-
>
|
|
71
|
-
<template #reference>
|
|
72
|
-
<MapSvgIcon icon="help" class="help" />
|
|
73
|
-
</template>
|
|
74
|
-
<div>
|
|
75
|
-
<strong>Within categories:</strong> OR
|
|
76
|
-
<br />
|
|
77
|
-
example: 'heart' OR 'colon'
|
|
78
|
-
<br />
|
|
79
|
-
<br />
|
|
80
|
-
<strong>Between categories:</strong> AND
|
|
81
|
-
<br />
|
|
82
|
-
example: 'rat' AND 'lung'
|
|
83
|
-
</div>
|
|
84
|
-
</el-popover>
|
|
85
|
-
</span>
|
|
86
|
-
</transition>
|
|
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>
|
|
103
|
-
</div>
|
|
104
|
-
</template>
|
|
105
|
-
|
|
106
|
-
<script>
|
|
107
|
-
/* eslint-disable no-alert, no-console */
|
|
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'
|
|
116
|
-
import { MapSvgIcon, MapSvgSpriteColor } from "@abi-software/svg-sprite";
|
|
117
|
-
import '@abi-software/svg-sprite/dist/style.css'
|
|
118
|
-
|
|
119
|
-
import { AlgoliaClient } from '../algolia/algolia.js'
|
|
120
|
-
import { facetPropPathMapping } from '../algolia/utils.js'
|
|
121
|
-
|
|
122
|
-
const capitalise = function (txt) {
|
|
123
|
-
return txt.charAt(0).toUpperCase() + txt.slice(1)
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
const convertReadableLabel = function (original) {
|
|
127
|
-
const name = original.toLowerCase()
|
|
128
|
-
if (speciesMap[name]) {
|
|
129
|
-
return capitalise(speciesMap[name])
|
|
130
|
-
} else {
|
|
131
|
-
return capitalise(name)
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
export default {
|
|
136
|
-
name: 'SearchFilters',
|
|
137
|
-
components: {
|
|
138
|
-
MapSvgIcon,
|
|
139
|
-
MapSvgSpriteColor,
|
|
140
|
-
Option,
|
|
141
|
-
Select,
|
|
142
|
-
Popover,
|
|
143
|
-
Cascader
|
|
144
|
-
},
|
|
145
|
-
props: {
|
|
146
|
-
/**
|
|
147
|
-
* Object containing information for
|
|
148
|
-
* the required viewing.
|
|
149
|
-
*/
|
|
150
|
-
entry: Object,
|
|
151
|
-
envVars: {
|
|
152
|
-
type: Object,
|
|
153
|
-
default: () => {},
|
|
154
|
-
},
|
|
155
|
-
},
|
|
156
|
-
data: function () {
|
|
157
|
-
return {
|
|
158
|
-
cascaderIsReady: false,
|
|
159
|
-
previousShowAllChecked: {
|
|
160
|
-
species: false,
|
|
161
|
-
gender: false,
|
|
162
|
-
organ: false,
|
|
163
|
-
datasets: false,
|
|
164
|
-
},
|
|
165
|
-
showFilters: true,
|
|
166
|
-
showFiltersText: true,
|
|
167
|
-
cascadeSelected: [],
|
|
168
|
-
cascadeSelectedWithBoolean: [],
|
|
169
|
-
numberShown: 10,
|
|
170
|
-
filters: [],
|
|
171
|
-
facets: ['Species', 'Gender', 'Organ', 'Datasets'],
|
|
172
|
-
numberDatasetsShown: ['10', '20', '50'],
|
|
173
|
-
props: { multiple: true },
|
|
174
|
-
options: [
|
|
175
|
-
{
|
|
176
|
-
value: 'Species',
|
|
177
|
-
label: 'Species',
|
|
178
|
-
children: [{}],
|
|
179
|
-
},
|
|
180
|
-
],
|
|
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 }
|
|
192
|
-
},
|
|
193
|
-
computed: {
|
|
194
|
-
numberOfResultsText: function () {
|
|
195
|
-
return `${this.entry.numberOfHits} results | Showing`
|
|
196
|
-
},
|
|
197
|
-
},
|
|
198
|
-
methods: {
|
|
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
|
|
212
|
-
},
|
|
213
|
-
populateCascader: function () {
|
|
214
|
-
return new Promise((resolve) => {
|
|
215
|
-
// Algolia facet serach
|
|
216
|
-
this.algoliaClient
|
|
217
|
-
.getAlgoliaFacets(facetPropPathMapping)
|
|
218
|
-
.then((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
|
-
})
|
|
262
|
-
})
|
|
263
|
-
.finally(() => {
|
|
264
|
-
resolve()
|
|
265
|
-
})
|
|
266
|
-
})
|
|
267
|
-
},
|
|
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)
|
|
309
|
-
},
|
|
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
|
-
})
|
|
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
|
|
365
|
-
},
|
|
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
|
|
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
|
|
404
|
-
},
|
|
405
|
-
// cascadeEvent: initiate searches based off cascader changes
|
|
406
|
-
cascadeEvent: function (eventIn) {
|
|
407
|
-
let event = [...eventIn]
|
|
408
|
-
if (event) {
|
|
409
|
-
// Check for show all in selected cascade options
|
|
410
|
-
|
|
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
|
|
422
|
-
}
|
|
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
|
-
})
|
|
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
|
-
|
|
470
|
-
this.$emit('loading', true) // let sidebarcontent wait for the requests
|
|
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
|
|
474
|
-
}
|
|
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
|
-
},
|
|
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
|
|
515
|
-
// *NOTE* Does NOT remove 'Show all' selections from showing in 'cascadeSelected'
|
|
516
|
-
showAllEventModifier: function (event) {
|
|
517
|
-
// check if show all is in the cascader checked option list
|
|
518
|
-
let hasShowAll = event
|
|
519
|
-
.map((ev) => (ev ? ev[1].toLowerCase().includes('show all') : false))
|
|
520
|
-
.includes(true)
|
|
521
|
-
// remove all selected options below the show all if checked
|
|
522
|
-
if (hasShowAll) {
|
|
523
|
-
let modifiedEvent = []
|
|
524
|
-
let facetMaps = {}
|
|
525
|
-
//catagorised different facet items
|
|
526
|
-
for (const i in event) {
|
|
527
|
-
if (facetMaps[event[i][0]] === undefined) facetMaps[event[i][0]] = []
|
|
528
|
-
facetMaps[event[i][0]].push(event[i])
|
|
529
|
-
}
|
|
530
|
-
// go through each facets
|
|
531
|
-
for (const facet in facetMaps) {
|
|
532
|
-
let showAll = undefined
|
|
533
|
-
// Find the show all item if any
|
|
534
|
-
for (let i = facetMaps[facet].length - 1; i >= 0; i--) {
|
|
535
|
-
if (facetMaps[facet][i][1].toLowerCase().includes('show all')) {
|
|
536
|
-
//seperate the showAll item and the rest
|
|
537
|
-
showAll = facetMaps[facet].splice(i, 1)[0]
|
|
538
|
-
break
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
if (showAll) {
|
|
542
|
-
if (this.previousShowAllChecked[facet]) {
|
|
543
|
-
//Unset the show all if it was present previously
|
|
544
|
-
//and there are other items
|
|
545
|
-
if (facetMaps[facet].length > 0)
|
|
546
|
-
modifiedEvent.push(...facetMaps[facet])
|
|
547
|
-
else modifiedEvent.push(showAll)
|
|
548
|
-
} else {
|
|
549
|
-
//showAll is turned on
|
|
550
|
-
modifiedEvent.push(showAll)
|
|
551
|
-
}
|
|
552
|
-
} else {
|
|
553
|
-
modifiedEvent.push(...facetMaps[facet])
|
|
554
|
-
}
|
|
555
|
-
}
|
|
556
|
-
//Make sure the expanded item are sorted first.
|
|
557
|
-
return modifiedEvent.sort((a, b) => {
|
|
558
|
-
if (this.__expandItem__) {
|
|
559
|
-
if (a[0] == this.__expandItem__) {
|
|
560
|
-
if (b[0] == this.__expandItem__) {
|
|
561
|
-
return 0
|
|
562
|
-
} else {
|
|
563
|
-
return -1
|
|
564
|
-
}
|
|
565
|
-
} else if (b[0] == this.__expandItem__) {
|
|
566
|
-
if (a[0] == this.__expandItem__) {
|
|
567
|
-
return 0
|
|
568
|
-
} else {
|
|
569
|
-
return 1
|
|
570
|
-
}
|
|
571
|
-
} else {
|
|
572
|
-
return 0
|
|
573
|
-
}
|
|
574
|
-
} else return 0
|
|
575
|
-
})
|
|
576
|
-
}
|
|
577
|
-
return event
|
|
578
|
-
},
|
|
579
|
-
cascadeExpandChange: function (event) {
|
|
580
|
-
//work around as the expand item may change on modifying the cascade props
|
|
581
|
-
this.__expandItem__ = event
|
|
582
|
-
this.cssMods()
|
|
583
|
-
},
|
|
584
|
-
numberShownChanged: function (event) {
|
|
585
|
-
this.$emit('numberPerPage', parseInt(event))
|
|
586
|
-
},
|
|
587
|
-
updatePreviousShowAllChecked: function (options) {
|
|
588
|
-
//Reset the states
|
|
589
|
-
for (const facet in this.previousShowAllChecked) {
|
|
590
|
-
this.previousShowAllChecked[facet] = false
|
|
591
|
-
}
|
|
592
|
-
options.forEach((element) => {
|
|
593
|
-
if (element[1].toLowerCase().includes('show all'))
|
|
594
|
-
this.previousShowAllChecked[element[0]] = true
|
|
595
|
-
})
|
|
596
|
-
},
|
|
597
|
-
// setCascader: Clears previous selections and takes in an array of facets to select: filterFacets
|
|
598
|
-
// facets are in the form:
|
|
599
|
-
// {
|
|
600
|
-
// facetPropPath: 'anatomy.organ.name',
|
|
601
|
-
// term: 'Sex',
|
|
602
|
-
// facet: 'Male'
|
|
603
|
-
// AND: true // Optional value for setting the boolean within a facet
|
|
604
|
-
// }
|
|
605
|
-
setCascader: function (filterFacets) {
|
|
606
|
-
//Do not set the value unless it is ready
|
|
607
|
-
if (this.cascaderIsReady && filterFacets && filterFacets.length != 0) {
|
|
608
|
-
//An inner function only used by this function
|
|
609
|
-
const createFilter = (e) => {
|
|
610
|
-
let filters = [
|
|
611
|
-
e.facetPropPath,
|
|
612
|
-
this.createCascaderItemValue(capitalise(e.term), e.facet),
|
|
613
|
-
]
|
|
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
|
-
}
|
|
626
|
-
|
|
627
|
-
this.cascadeSelected = filterFacets.map((e) => {
|
|
628
|
-
let filters = createFilter(e)
|
|
629
|
-
return filters
|
|
630
|
-
})
|
|
631
|
-
|
|
632
|
-
// Unforttunately the cascader is very particular about it's v-model
|
|
633
|
-
// to get around this we create a clone of it and use this clone for adding our boolean information
|
|
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)
|
|
640
|
-
}
|
|
641
|
-
this.tagsChangedCallback(filterFacets);
|
|
642
|
-
},
|
|
643
|
-
addFilter: function (filterToAdd) {
|
|
644
|
-
//Do not set the value unless it is ready
|
|
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
|
-
])
|
|
668
|
-
// The 'AND' her is to set the boolean value when we search on the filters. It can be undefined without breaking anything
|
|
669
|
-
return true
|
|
670
|
-
}
|
|
671
|
-
}
|
|
672
|
-
},
|
|
673
|
-
initiateSearch: function () {
|
|
674
|
-
this.cascadeEvent(this.cascadeSelectedWithBoolean)
|
|
675
|
-
},
|
|
676
|
-
// checkShowAllBoxes: Checks each 'Show all' cascade option by using the setCascader function
|
|
677
|
-
checkShowAllBoxes: function () {
|
|
678
|
-
this.setCascader(
|
|
679
|
-
this.options.map((option) => {
|
|
680
|
-
return {
|
|
681
|
-
facetPropPath: option.value,
|
|
682
|
-
term: option.label,
|
|
683
|
-
facet: 'Show all',
|
|
684
|
-
}
|
|
685
|
-
})
|
|
686
|
-
)
|
|
687
|
-
},
|
|
688
|
-
makeCascadeLabelsClickable: function () {
|
|
689
|
-
// Next tick allows the cascader menu to change
|
|
690
|
-
this.$nextTick(() => {
|
|
691
|
-
document
|
|
692
|
-
.querySelectorAll('.sidebar-cascader-popper .el-cascader-node__label')
|
|
693
|
-
.forEach((el) => {
|
|
694
|
-
// step through each cascade label
|
|
695
|
-
el.onclick = function () {
|
|
696
|
-
const checkbox = this.previousElementSibling
|
|
697
|
-
if (checkbox) {
|
|
698
|
-
if (!checkbox.parentElement.attributes['aria-owns']) {
|
|
699
|
-
// check if we are at the lowest level of cascader
|
|
700
|
-
this.previousElementSibling.click() // Click the checkbox
|
|
701
|
-
}
|
|
702
|
-
}
|
|
703
|
-
}
|
|
704
|
-
})
|
|
705
|
-
})
|
|
706
|
-
},
|
|
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
|
|
727
|
-
*/
|
|
728
|
-
validateAndConvertFilterToHierarchical: function (filter) {
|
|
729
|
-
if (filter && filter.facet && filter.term) {
|
|
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
|
-
}
|
|
755
|
-
}
|
|
756
|
-
}
|
|
757
|
-
return false
|
|
758
|
-
},
|
|
759
|
-
getHierarchicalValidatedFilters: function (filters) {
|
|
760
|
-
if (filters) {
|
|
761
|
-
if (this.cascaderIsReady) {
|
|
762
|
-
const result = []
|
|
763
|
-
filters.forEach((filter) => {
|
|
764
|
-
const validatedFilter =
|
|
765
|
-
this.validateAndConvertFilterToHierarchical(filter)
|
|
766
|
-
if (validatedFilter) {
|
|
767
|
-
result.push(validatedFilter)
|
|
768
|
-
}
|
|
769
|
-
})
|
|
770
|
-
return result
|
|
771
|
-
} else return filters
|
|
772
|
-
}
|
|
773
|
-
return []
|
|
774
|
-
},
|
|
775
|
-
},
|
|
776
|
-
mounted: function () {
|
|
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
|
-
})
|
|
790
|
-
},
|
|
791
|
-
}
|
|
792
|
-
</script>
|
|
793
|
-
|
|
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
|
-
}
|
|
825
|
-
|
|
826
|
-
.filter-default-value {
|
|
827
|
-
pointer-events: none;
|
|
828
|
-
position: absolute;
|
|
829
|
-
top: 0;
|
|
830
|
-
left: 0;
|
|
831
|
-
padding-top: 10px;
|
|
832
|
-
padding-left: 16px;
|
|
833
|
-
}
|
|
834
|
-
|
|
835
|
-
.help {
|
|
836
|
-
width: 24px !important;
|
|
837
|
-
height: 24px;
|
|
838
|
-
transform: scale(1.1);
|
|
839
|
-
cursor: pointer;
|
|
840
|
-
}
|
|
841
|
-
|
|
842
|
-
.popover {
|
|
843
|
-
color: rgb(48, 49, 51);
|
|
844
|
-
font-family: Asap;
|
|
845
|
-
margin: 12px;
|
|
846
|
-
}
|
|
847
|
-
|
|
848
|
-
.filter-icon-inside {
|
|
849
|
-
width: 12px !important;
|
|
850
|
-
height: 12px !important;
|
|
851
|
-
color: #292b66;
|
|
852
|
-
transform: scale(2) !important;
|
|
853
|
-
margin-bottom: 0px !important;
|
|
854
|
-
}
|
|
855
|
-
|
|
856
|
-
.cascader {
|
|
857
|
-
font-family: Asap;
|
|
858
|
-
font-size: 14px;
|
|
859
|
-
font-weight: 500;
|
|
860
|
-
font-stretch: normal;
|
|
861
|
-
font-style: normal;
|
|
862
|
-
line-height: normal;
|
|
863
|
-
letter-spacing: normal;
|
|
864
|
-
color: #292b66;
|
|
865
|
-
text-align: center;
|
|
866
|
-
padding-bottom: 6px;
|
|
867
|
-
}
|
|
868
|
-
|
|
869
|
-
.dataset-shown {
|
|
870
|
-
display: flex;
|
|
871
|
-
flex-direction: row;
|
|
872
|
-
float: right;
|
|
873
|
-
padding-bottom: 6px;
|
|
874
|
-
gap: 8px;
|
|
875
|
-
}
|
|
876
|
-
|
|
877
|
-
.dataset-results-feedback {
|
|
878
|
-
white-space:nowrap;
|
|
879
|
-
text-align: right;
|
|
880
|
-
color: rgb(48, 49, 51);
|
|
881
|
-
font-family: Asap;
|
|
882
|
-
font-size: 18px;
|
|
883
|
-
font-weight: 500;
|
|
884
|
-
padding-top: 8px;
|
|
885
|
-
}
|
|
886
|
-
|
|
887
|
-
.search-filters {
|
|
888
|
-
position: relative;
|
|
889
|
-
float: left;
|
|
890
|
-
padding-right: 15px;
|
|
891
|
-
}
|
|
892
|
-
|
|
893
|
-
.number-shown-select :deep(.el-select__wrapper) {
|
|
894
|
-
width: 68px;
|
|
895
|
-
height: 40px;
|
|
896
|
-
color: rgb(48, 49, 51);
|
|
897
|
-
}
|
|
898
|
-
|
|
899
|
-
.el-select-dropdown__item.is-selected {
|
|
900
|
-
color: #8300BF;
|
|
901
|
-
}
|
|
902
|
-
|
|
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;
|
|
910
|
-
}
|
|
911
|
-
|
|
912
|
-
.filters :deep(.el-popover[x-placement^='top'] .popper__arrow) {
|
|
913
|
-
border-top-color: $app-primary-color;
|
|
914
|
-
border-bottom-width: 0;
|
|
915
|
-
}
|
|
916
|
-
.filters :deep(.el-popover[x-placement^='top'] .popper__arrow::after) {
|
|
917
|
-
border-top-color: #f3ecf6;
|
|
918
|
-
border-bottom-width: 0;
|
|
919
|
-
}
|
|
920
|
-
|
|
921
|
-
.filters :deep(.el-popover[x-placement^='bottom'] .popper__arrow) {
|
|
922
|
-
border-top-width: 0;
|
|
923
|
-
border-bottom-color: $app-primary-color;
|
|
924
|
-
}
|
|
925
|
-
.filters :deep(.el-popover[x-placement^='bottom'] .popper__arrow::after) {
|
|
926
|
-
border-top-width: 0;
|
|
927
|
-
border-bottom-color: #f3ecf6;
|
|
928
|
-
}
|
|
929
|
-
|
|
930
|
-
.filters :deep(.el-popover[x-placement^='right'] .popper__arrow) {
|
|
931
|
-
border-right-color: $app-primary-color;
|
|
932
|
-
border-left-width: 0;
|
|
933
|
-
}
|
|
934
|
-
.filters :deep(.el-popover[x-placement^='right'] .popper__arrow::after) {
|
|
935
|
-
border-right-color: #f3ecf6;
|
|
936
|
-
border-left-width: 0;
|
|
937
|
-
}
|
|
938
|
-
|
|
939
|
-
.filters :deep(.el-popover[x-placement^='left'] .popper__arrow) {
|
|
940
|
-
border-right-width: 0;
|
|
941
|
-
border-left-color: $app-primary-color;
|
|
942
|
-
}
|
|
943
|
-
.filters :deep(.el-popover[x-placement^='left'] .popper__arrow::after) {
|
|
944
|
-
border-right-width: 0;
|
|
945
|
-
border-left-color: #f3ecf6;
|
|
946
|
-
}
|
|
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>
|
|
1
|
+
<template>
|
|
2
|
+
<div class="filters">
|
|
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>
|
|
46
|
+
<transition name="el-zoom-in-top">
|
|
47
|
+
<span v-show="showFilters" v-loading="!cascaderIsReady" class="search-filters transition-box">
|
|
48
|
+
<el-cascader
|
|
49
|
+
class="cascader"
|
|
50
|
+
ref="cascader"
|
|
51
|
+
v-model="cascadeSelected"
|
|
52
|
+
size="large"
|
|
53
|
+
placeholder=" "
|
|
54
|
+
:collapse-tags="true"
|
|
55
|
+
collapse-tags-tooltip
|
|
56
|
+
:options="options"
|
|
57
|
+
:props="props"
|
|
58
|
+
@change="cascadeEvent($event)"
|
|
59
|
+
@expand-change="cascadeExpandChange"
|
|
60
|
+
:show-all-levels="true"
|
|
61
|
+
popper-class="sidebar-cascader-popper"
|
|
62
|
+
/>
|
|
63
|
+
<div v-if="showFiltersText" class="filter-default-value">Filters</div>
|
|
64
|
+
<el-popover
|
|
65
|
+
title="How do filters work?"
|
|
66
|
+
width="250"
|
|
67
|
+
trigger="hover"
|
|
68
|
+
:append-to-body="false"
|
|
69
|
+
popper-class="popover"
|
|
70
|
+
>
|
|
71
|
+
<template #reference>
|
|
72
|
+
<MapSvgIcon icon="help" class="help" />
|
|
73
|
+
</template>
|
|
74
|
+
<div>
|
|
75
|
+
<strong>Within categories:</strong> OR
|
|
76
|
+
<br />
|
|
77
|
+
example: 'heart' OR 'colon'
|
|
78
|
+
<br />
|
|
79
|
+
<br />
|
|
80
|
+
<strong>Between categories:</strong> AND
|
|
81
|
+
<br />
|
|
82
|
+
example: 'rat' AND 'lung'
|
|
83
|
+
</div>
|
|
84
|
+
</el-popover>
|
|
85
|
+
</span>
|
|
86
|
+
</transition>
|
|
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>
|
|
103
|
+
</div>
|
|
104
|
+
</template>
|
|
105
|
+
|
|
106
|
+
<script>
|
|
107
|
+
/* eslint-disable no-alert, no-console */
|
|
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'
|
|
116
|
+
import { MapSvgIcon, MapSvgSpriteColor } from "@abi-software/svg-sprite";
|
|
117
|
+
import '@abi-software/svg-sprite/dist/style.css'
|
|
118
|
+
|
|
119
|
+
import { AlgoliaClient } from '../algolia/algolia.js'
|
|
120
|
+
import { facetPropPathMapping } from '../algolia/utils.js'
|
|
121
|
+
|
|
122
|
+
const capitalise = function (txt) {
|
|
123
|
+
return txt.charAt(0).toUpperCase() + txt.slice(1)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const convertReadableLabel = function (original) {
|
|
127
|
+
const name = original.toLowerCase()
|
|
128
|
+
if (speciesMap[name]) {
|
|
129
|
+
return capitalise(speciesMap[name])
|
|
130
|
+
} else {
|
|
131
|
+
return capitalise(name)
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export default {
|
|
136
|
+
name: 'SearchFilters',
|
|
137
|
+
components: {
|
|
138
|
+
MapSvgIcon,
|
|
139
|
+
MapSvgSpriteColor,
|
|
140
|
+
Option,
|
|
141
|
+
Select,
|
|
142
|
+
Popover,
|
|
143
|
+
Cascader
|
|
144
|
+
},
|
|
145
|
+
props: {
|
|
146
|
+
/**
|
|
147
|
+
* Object containing information for
|
|
148
|
+
* the required viewing.
|
|
149
|
+
*/
|
|
150
|
+
entry: Object,
|
|
151
|
+
envVars: {
|
|
152
|
+
type: Object,
|
|
153
|
+
default: () => {},
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
data: function () {
|
|
157
|
+
return {
|
|
158
|
+
cascaderIsReady: false,
|
|
159
|
+
previousShowAllChecked: {
|
|
160
|
+
species: false,
|
|
161
|
+
gender: false,
|
|
162
|
+
organ: false,
|
|
163
|
+
datasets: false,
|
|
164
|
+
},
|
|
165
|
+
showFilters: true,
|
|
166
|
+
showFiltersText: true,
|
|
167
|
+
cascadeSelected: [],
|
|
168
|
+
cascadeSelectedWithBoolean: [],
|
|
169
|
+
numberShown: 10,
|
|
170
|
+
filters: [],
|
|
171
|
+
facets: ['Species', 'Gender', 'Organ', 'Datasets'],
|
|
172
|
+
numberDatasetsShown: ['10', '20', '50'],
|
|
173
|
+
props: { multiple: true },
|
|
174
|
+
options: [
|
|
175
|
+
{
|
|
176
|
+
value: 'Species',
|
|
177
|
+
label: 'Species',
|
|
178
|
+
children: [{}],
|
|
179
|
+
},
|
|
180
|
+
],
|
|
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 }
|
|
192
|
+
},
|
|
193
|
+
computed: {
|
|
194
|
+
numberOfResultsText: function () {
|
|
195
|
+
return `${this.entry.numberOfHits} results | Showing`
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
methods: {
|
|
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
|
|
212
|
+
},
|
|
213
|
+
populateCascader: function () {
|
|
214
|
+
return new Promise((resolve) => {
|
|
215
|
+
// Algolia facet serach
|
|
216
|
+
this.algoliaClient
|
|
217
|
+
.getAlgoliaFacets(facetPropPathMapping)
|
|
218
|
+
.then((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
|
+
})
|
|
262
|
+
})
|
|
263
|
+
.finally(() => {
|
|
264
|
+
resolve()
|
|
265
|
+
})
|
|
266
|
+
})
|
|
267
|
+
},
|
|
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)
|
|
309
|
+
},
|
|
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
|
+
})
|
|
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
|
|
365
|
+
},
|
|
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
|
|
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
|
|
404
|
+
},
|
|
405
|
+
// cascadeEvent: initiate searches based off cascader changes
|
|
406
|
+
cascadeEvent: function (eventIn) {
|
|
407
|
+
let event = [...eventIn]
|
|
408
|
+
if (event) {
|
|
409
|
+
// Check for show all in selected cascade options
|
|
410
|
+
|
|
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
|
|
422
|
+
}
|
|
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
|
+
})
|
|
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
|
+
|
|
470
|
+
this.$emit('loading', true) // let sidebarcontent wait for the requests
|
|
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
|
|
474
|
+
}
|
|
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
|
+
},
|
|
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
|
|
515
|
+
// *NOTE* Does NOT remove 'Show all' selections from showing in 'cascadeSelected'
|
|
516
|
+
showAllEventModifier: function (event) {
|
|
517
|
+
// check if show all is in the cascader checked option list
|
|
518
|
+
let hasShowAll = event
|
|
519
|
+
.map((ev) => (ev ? ev[1].toLowerCase().includes('show all') : false))
|
|
520
|
+
.includes(true)
|
|
521
|
+
// remove all selected options below the show all if checked
|
|
522
|
+
if (hasShowAll) {
|
|
523
|
+
let modifiedEvent = []
|
|
524
|
+
let facetMaps = {}
|
|
525
|
+
//catagorised different facet items
|
|
526
|
+
for (const i in event) {
|
|
527
|
+
if (facetMaps[event[i][0]] === undefined) facetMaps[event[i][0]] = []
|
|
528
|
+
facetMaps[event[i][0]].push(event[i])
|
|
529
|
+
}
|
|
530
|
+
// go through each facets
|
|
531
|
+
for (const facet in facetMaps) {
|
|
532
|
+
let showAll = undefined
|
|
533
|
+
// Find the show all item if any
|
|
534
|
+
for (let i = facetMaps[facet].length - 1; i >= 0; i--) {
|
|
535
|
+
if (facetMaps[facet][i][1].toLowerCase().includes('show all')) {
|
|
536
|
+
//seperate the showAll item and the rest
|
|
537
|
+
showAll = facetMaps[facet].splice(i, 1)[0]
|
|
538
|
+
break
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
if (showAll) {
|
|
542
|
+
if (this.previousShowAllChecked[facet]) {
|
|
543
|
+
//Unset the show all if it was present previously
|
|
544
|
+
//and there are other items
|
|
545
|
+
if (facetMaps[facet].length > 0)
|
|
546
|
+
modifiedEvent.push(...facetMaps[facet])
|
|
547
|
+
else modifiedEvent.push(showAll)
|
|
548
|
+
} else {
|
|
549
|
+
//showAll is turned on
|
|
550
|
+
modifiedEvent.push(showAll)
|
|
551
|
+
}
|
|
552
|
+
} else {
|
|
553
|
+
modifiedEvent.push(...facetMaps[facet])
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
//Make sure the expanded item are sorted first.
|
|
557
|
+
return modifiedEvent.sort((a, b) => {
|
|
558
|
+
if (this.__expandItem__) {
|
|
559
|
+
if (a[0] == this.__expandItem__) {
|
|
560
|
+
if (b[0] == this.__expandItem__) {
|
|
561
|
+
return 0
|
|
562
|
+
} else {
|
|
563
|
+
return -1
|
|
564
|
+
}
|
|
565
|
+
} else if (b[0] == this.__expandItem__) {
|
|
566
|
+
if (a[0] == this.__expandItem__) {
|
|
567
|
+
return 0
|
|
568
|
+
} else {
|
|
569
|
+
return 1
|
|
570
|
+
}
|
|
571
|
+
} else {
|
|
572
|
+
return 0
|
|
573
|
+
}
|
|
574
|
+
} else return 0
|
|
575
|
+
})
|
|
576
|
+
}
|
|
577
|
+
return event
|
|
578
|
+
},
|
|
579
|
+
cascadeExpandChange: function (event) {
|
|
580
|
+
//work around as the expand item may change on modifying the cascade props
|
|
581
|
+
this.__expandItem__ = event
|
|
582
|
+
this.cssMods()
|
|
583
|
+
},
|
|
584
|
+
numberShownChanged: function (event) {
|
|
585
|
+
this.$emit('numberPerPage', parseInt(event))
|
|
586
|
+
},
|
|
587
|
+
updatePreviousShowAllChecked: function (options) {
|
|
588
|
+
//Reset the states
|
|
589
|
+
for (const facet in this.previousShowAllChecked) {
|
|
590
|
+
this.previousShowAllChecked[facet] = false
|
|
591
|
+
}
|
|
592
|
+
options.forEach((element) => {
|
|
593
|
+
if (element[1].toLowerCase().includes('show all'))
|
|
594
|
+
this.previousShowAllChecked[element[0]] = true
|
|
595
|
+
})
|
|
596
|
+
},
|
|
597
|
+
// setCascader: Clears previous selections and takes in an array of facets to select: filterFacets
|
|
598
|
+
// facets are in the form:
|
|
599
|
+
// {
|
|
600
|
+
// facetPropPath: 'anatomy.organ.name',
|
|
601
|
+
// term: 'Sex',
|
|
602
|
+
// facet: 'Male'
|
|
603
|
+
// AND: true // Optional value for setting the boolean within a facet
|
|
604
|
+
// }
|
|
605
|
+
setCascader: function (filterFacets) {
|
|
606
|
+
//Do not set the value unless it is ready
|
|
607
|
+
if (this.cascaderIsReady && filterFacets && filterFacets.length != 0) {
|
|
608
|
+
//An inner function only used by this function
|
|
609
|
+
const createFilter = (e) => {
|
|
610
|
+
let filters = [
|
|
611
|
+
e.facetPropPath,
|
|
612
|
+
this.createCascaderItemValue(capitalise(e.term), e.facet),
|
|
613
|
+
]
|
|
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
|
+
}
|
|
626
|
+
|
|
627
|
+
this.cascadeSelected = filterFacets.map((e) => {
|
|
628
|
+
let filters = createFilter(e)
|
|
629
|
+
return filters
|
|
630
|
+
})
|
|
631
|
+
|
|
632
|
+
// Unforttunately the cascader is very particular about it's v-model
|
|
633
|
+
// to get around this we create a clone of it and use this clone for adding our boolean information
|
|
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)
|
|
640
|
+
}
|
|
641
|
+
this.tagsChangedCallback(filterFacets);
|
|
642
|
+
},
|
|
643
|
+
addFilter: function (filterToAdd) {
|
|
644
|
+
//Do not set the value unless it is ready
|
|
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
|
+
])
|
|
668
|
+
// The 'AND' her is to set the boolean value when we search on the filters. It can be undefined without breaking anything
|
|
669
|
+
return true
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
},
|
|
673
|
+
initiateSearch: function () {
|
|
674
|
+
this.cascadeEvent(this.cascadeSelectedWithBoolean)
|
|
675
|
+
},
|
|
676
|
+
// checkShowAllBoxes: Checks each 'Show all' cascade option by using the setCascader function
|
|
677
|
+
checkShowAllBoxes: function () {
|
|
678
|
+
this.setCascader(
|
|
679
|
+
this.options.map((option) => {
|
|
680
|
+
return {
|
|
681
|
+
facetPropPath: option.value,
|
|
682
|
+
term: option.label,
|
|
683
|
+
facet: 'Show all',
|
|
684
|
+
}
|
|
685
|
+
})
|
|
686
|
+
)
|
|
687
|
+
},
|
|
688
|
+
makeCascadeLabelsClickable: function () {
|
|
689
|
+
// Next tick allows the cascader menu to change
|
|
690
|
+
this.$nextTick(() => {
|
|
691
|
+
document
|
|
692
|
+
.querySelectorAll('.sidebar-cascader-popper .el-cascader-node__label')
|
|
693
|
+
.forEach((el) => {
|
|
694
|
+
// step through each cascade label
|
|
695
|
+
el.onclick = function () {
|
|
696
|
+
const checkbox = this.previousElementSibling
|
|
697
|
+
if (checkbox) {
|
|
698
|
+
if (!checkbox.parentElement.attributes['aria-owns']) {
|
|
699
|
+
// check if we are at the lowest level of cascader
|
|
700
|
+
this.previousElementSibling.click() // Click the checkbox
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
})
|
|
705
|
+
})
|
|
706
|
+
},
|
|
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
|
|
727
|
+
*/
|
|
728
|
+
validateAndConvertFilterToHierarchical: function (filter) {
|
|
729
|
+
if (filter && filter.facet && filter.term) {
|
|
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
|
+
}
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
return false
|
|
758
|
+
},
|
|
759
|
+
getHierarchicalValidatedFilters: function (filters) {
|
|
760
|
+
if (filters) {
|
|
761
|
+
if (this.cascaderIsReady) {
|
|
762
|
+
const result = []
|
|
763
|
+
filters.forEach((filter) => {
|
|
764
|
+
const validatedFilter =
|
|
765
|
+
this.validateAndConvertFilterToHierarchical(filter)
|
|
766
|
+
if (validatedFilter) {
|
|
767
|
+
result.push(validatedFilter)
|
|
768
|
+
}
|
|
769
|
+
})
|
|
770
|
+
return result
|
|
771
|
+
} else return filters
|
|
772
|
+
}
|
|
773
|
+
return []
|
|
774
|
+
},
|
|
775
|
+
},
|
|
776
|
+
mounted: function () {
|
|
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
|
+
})
|
|
790
|
+
},
|
|
791
|
+
}
|
|
792
|
+
</script>
|
|
793
|
+
|
|
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
|
+
}
|
|
825
|
+
|
|
826
|
+
.filter-default-value {
|
|
827
|
+
pointer-events: none;
|
|
828
|
+
position: absolute;
|
|
829
|
+
top: 0;
|
|
830
|
+
left: 0;
|
|
831
|
+
padding-top: 10px;
|
|
832
|
+
padding-left: 16px;
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
.help {
|
|
836
|
+
width: 24px !important;
|
|
837
|
+
height: 24px;
|
|
838
|
+
transform: scale(1.1);
|
|
839
|
+
cursor: pointer;
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
.popover {
|
|
843
|
+
color: rgb(48, 49, 51);
|
|
844
|
+
font-family: Asap;
|
|
845
|
+
margin: 12px;
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
.filter-icon-inside {
|
|
849
|
+
width: 12px !important;
|
|
850
|
+
height: 12px !important;
|
|
851
|
+
color: #292b66;
|
|
852
|
+
transform: scale(2) !important;
|
|
853
|
+
margin-bottom: 0px !important;
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
.cascader {
|
|
857
|
+
font-family: Asap;
|
|
858
|
+
font-size: 14px;
|
|
859
|
+
font-weight: 500;
|
|
860
|
+
font-stretch: normal;
|
|
861
|
+
font-style: normal;
|
|
862
|
+
line-height: normal;
|
|
863
|
+
letter-spacing: normal;
|
|
864
|
+
color: #292b66;
|
|
865
|
+
text-align: center;
|
|
866
|
+
padding-bottom: 6px;
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
.dataset-shown {
|
|
870
|
+
display: flex;
|
|
871
|
+
flex-direction: row;
|
|
872
|
+
float: right;
|
|
873
|
+
padding-bottom: 6px;
|
|
874
|
+
gap: 8px;
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
.dataset-results-feedback {
|
|
878
|
+
white-space:nowrap;
|
|
879
|
+
text-align: right;
|
|
880
|
+
color: rgb(48, 49, 51);
|
|
881
|
+
font-family: Asap;
|
|
882
|
+
font-size: 18px;
|
|
883
|
+
font-weight: 500;
|
|
884
|
+
padding-top: 8px;
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
.search-filters {
|
|
888
|
+
position: relative;
|
|
889
|
+
float: left;
|
|
890
|
+
padding-right: 15px;
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
.number-shown-select :deep(.el-select__wrapper) {
|
|
894
|
+
width: 68px;
|
|
895
|
+
height: 40px;
|
|
896
|
+
color: rgb(48, 49, 51);
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
.el-select-dropdown__item.is-selected {
|
|
900
|
+
color: #8300BF;
|
|
901
|
+
}
|
|
902
|
+
|
|
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;
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
.filters :deep(.el-popover[x-placement^='top'] .popper__arrow) {
|
|
913
|
+
border-top-color: $app-primary-color;
|
|
914
|
+
border-bottom-width: 0;
|
|
915
|
+
}
|
|
916
|
+
.filters :deep(.el-popover[x-placement^='top'] .popper__arrow::after) {
|
|
917
|
+
border-top-color: #f3ecf6;
|
|
918
|
+
border-bottom-width: 0;
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
.filters :deep(.el-popover[x-placement^='bottom'] .popper__arrow) {
|
|
922
|
+
border-top-width: 0;
|
|
923
|
+
border-bottom-color: $app-primary-color;
|
|
924
|
+
}
|
|
925
|
+
.filters :deep(.el-popover[x-placement^='bottom'] .popper__arrow::after) {
|
|
926
|
+
border-top-width: 0;
|
|
927
|
+
border-bottom-color: #f3ecf6;
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
.filters :deep(.el-popover[x-placement^='right'] .popper__arrow) {
|
|
931
|
+
border-right-color: $app-primary-color;
|
|
932
|
+
border-left-width: 0;
|
|
933
|
+
}
|
|
934
|
+
.filters :deep(.el-popover[x-placement^='right'] .popper__arrow::after) {
|
|
935
|
+
border-right-color: #f3ecf6;
|
|
936
|
+
border-left-width: 0;
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
.filters :deep(.el-popover[x-placement^='left'] .popper__arrow) {
|
|
940
|
+
border-right-width: 0;
|
|
941
|
+
border-left-color: $app-primary-color;
|
|
942
|
+
}
|
|
943
|
+
.filters :deep(.el-popover[x-placement^='left'] .popper__arrow::after) {
|
|
944
|
+
border-right-width: 0;
|
|
945
|
+
border-left-color: #f3ecf6;
|
|
946
|
+
}
|
|
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>
|