@explorer-1/vue 0.2.56 → 0.2.57
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/package.json +1 -1
- package/src/components/SearchFilterGroup/SearchFilterGroup.stories.js +89 -0
- package/src/components/SearchFilterGroup/SearchFilterGroup.vue +165 -25
- package/src/components/SearchFilterGroupAccordionItem/SearchFilterGroupAccordionItem.vue +88 -0
- package/src/interfaces.ts +21 -0
- package/src/templates/PageImageDetail/PageImageDetail.vue +1 -1
package/package.json
CHANGED
|
@@ -58,3 +58,92 @@ export const DateFilter = {
|
|
|
58
58
|
truncateFilters: true
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
|
+
export const SubFilters = {
|
|
62
|
+
decorators: [
|
|
63
|
+
() => ({
|
|
64
|
+
template: '<div id="storyRoot" class="md:w-1/2 lg:w-1/3"><story /></div>'
|
|
65
|
+
})
|
|
66
|
+
],
|
|
67
|
+
args: {
|
|
68
|
+
filterBy: [],
|
|
69
|
+
buckets: [
|
|
70
|
+
{ key: 'Solar System', doc_count: 3308 },
|
|
71
|
+
{ key: 'Earth', doc_count: 1179 },
|
|
72
|
+
{ key: 'Stars and Galaxies', doc_count: 979 },
|
|
73
|
+
{ key: 'Technology', doc_count: 480 }
|
|
74
|
+
],
|
|
75
|
+
groupKey: 'topics',
|
|
76
|
+
groupTitle: 'Topic',
|
|
77
|
+
truncateFilters: false,
|
|
78
|
+
subFilters: {
|
|
79
|
+
solar_system: [
|
|
80
|
+
{
|
|
81
|
+
key: 'Sun',
|
|
82
|
+
agg: 'solar_system_area',
|
|
83
|
+
doc_count: 20
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
key: 'Mercury',
|
|
87
|
+
agg: 'solar_system_area',
|
|
88
|
+
doc_count: 21
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
key: 'Venus',
|
|
92
|
+
agg: 'solar_system_area',
|
|
93
|
+
doc_count: 22
|
|
94
|
+
}
|
|
95
|
+
],
|
|
96
|
+
earth: [
|
|
97
|
+
{
|
|
98
|
+
key: 'Sea Level',
|
|
99
|
+
agg: 'solar_system_area',
|
|
100
|
+
doc_count: 20
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
key: 'Pollution',
|
|
104
|
+
agg: 'solar_system_area',
|
|
105
|
+
doc_count: 21
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
key: 'Climate Change',
|
|
109
|
+
agg: 'solar_system_area',
|
|
110
|
+
doc_count: 22
|
|
111
|
+
}
|
|
112
|
+
],
|
|
113
|
+
stars_and_galaxies: [
|
|
114
|
+
{
|
|
115
|
+
key: 'Sea Level',
|
|
116
|
+
agg: 'solar_system_area',
|
|
117
|
+
doc_count: 20
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
key: 'Pollution',
|
|
121
|
+
agg: 'solar_system_area',
|
|
122
|
+
doc_count: 21
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
key: 'Climate Change',
|
|
126
|
+
agg: 'solar_system_area',
|
|
127
|
+
doc_count: 22
|
|
128
|
+
}
|
|
129
|
+
],
|
|
130
|
+
technology: [
|
|
131
|
+
{
|
|
132
|
+
key: 'Sea Level',
|
|
133
|
+
agg: 'solar_system_area',
|
|
134
|
+
doc_count: 20
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
key: 'Pollution',
|
|
138
|
+
agg: 'solar_system_area',
|
|
139
|
+
doc_count: 21
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
key: 'Climate Change',
|
|
143
|
+
agg: 'solar_system_area',
|
|
144
|
+
doc_count: 22
|
|
145
|
+
}
|
|
146
|
+
]
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
@@ -18,27 +18,98 @@
|
|
|
18
18
|
ref="buckets"
|
|
19
19
|
class="form-group form-check"
|
|
20
20
|
>
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
<SearchFilterGroupAccordionItem
|
|
22
|
+
v-if="hasSubFilters(bucket.key_as_string || bucket.key)"
|
|
23
|
+
class="w-auto"
|
|
24
|
+
:init-open="subFilterIsInQueryParams(bucket.key_as_string || bucket.key)"
|
|
25
25
|
>
|
|
26
|
-
<
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
26
|
+
<template #header>
|
|
27
|
+
<!-- correct for zero based index -->
|
|
28
|
+
<div
|
|
29
|
+
v-if="!truncateFilters || index <= checkbox.checkboxLimit - 1"
|
|
30
|
+
class="flex flex-grow"
|
|
31
|
+
>
|
|
32
|
+
<input
|
|
33
|
+
:id="
|
|
34
|
+
bucket.key_as_string
|
|
35
|
+
? generateId(bucket.key_as_string, groupKey)
|
|
36
|
+
: generateId(bucket.key, groupKey)
|
|
37
|
+
"
|
|
38
|
+
v-model="filterByHandler"
|
|
39
|
+
type="checkbox"
|
|
40
|
+
:value="bucket.key_as_string ? bucket.key_as_string : bucket.key"
|
|
41
|
+
class="text-primary focus:ring-2 focus:ring-primary flex-shrink-0 w-5 h-5 mt-px mr-1 align-middle border rounded-none"
|
|
42
|
+
/>
|
|
43
|
+
<!-- 'key_as_string' exists for dates to have a human readable version -->
|
|
44
|
+
<label
|
|
45
|
+
:for="
|
|
46
|
+
bucket.key_as_string
|
|
47
|
+
? generateId(bucket.key_as_string, groupKey)
|
|
48
|
+
: generateId(bucket.key, groupKey)
|
|
49
|
+
"
|
|
50
|
+
class="form-check-label pl-2 tracking-normal align-middle"
|
|
51
|
+
>
|
|
52
|
+
<span class="font-extrabold">{{
|
|
53
|
+
prettyFilterNames(bucket.key_as_string ? bucket.key_as_string : bucket.key)
|
|
54
|
+
}}</span>
|
|
55
|
+
<span class="text-gray-mid-dark font-normal text-sm">
|
|
56
|
+
({{ bucket.doc_count.toLocaleString() }})
|
|
57
|
+
</span>
|
|
58
|
+
</label>
|
|
59
|
+
</div>
|
|
60
|
+
</template>
|
|
61
|
+
<template #default>
|
|
62
|
+
<!-- dynamic slots for subFilters -->
|
|
63
|
+
<div
|
|
64
|
+
v-if="
|
|
65
|
+
(bucket.key_as_string || bucket.key) &&
|
|
66
|
+
getSubFilters(bucket.key_as_string || bucket.key) &&
|
|
67
|
+
subFilterParentKeys?.length
|
|
68
|
+
"
|
|
69
|
+
class="block"
|
|
70
|
+
>
|
|
71
|
+
<div
|
|
72
|
+
v-if="hasSubFilters(bucket.key_as_string || bucket.key)"
|
|
73
|
+
class="SubFilters"
|
|
74
|
+
>
|
|
75
|
+
<slot :name="`slot_${getSubFilterParentKey(bucket.key_as_string || bucket.key)}`" />
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
</template>
|
|
79
|
+
</SearchFilterGroupAccordionItem>
|
|
80
|
+
<template v-else>
|
|
81
|
+
<!-- correct for zero based index -->
|
|
82
|
+
<div
|
|
83
|
+
v-if="!truncateFilters || index <= checkbox.checkboxLimit - 1"
|
|
84
|
+
class="flex my-2"
|
|
37
85
|
>
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
86
|
+
<input
|
|
87
|
+
:id="
|
|
88
|
+
bucket.key_as_string
|
|
89
|
+
? generateId(bucket.key_as_string, groupKey)
|
|
90
|
+
: generateId(bucket.key, groupKey)
|
|
91
|
+
"
|
|
92
|
+
v-model="filterByHandler"
|
|
93
|
+
type="checkbox"
|
|
94
|
+
:value="bucket.key_as_string ? bucket.key_as_string : bucket.key"
|
|
95
|
+
class="text-primary focus:ring-2 focus:ring-primary flex-shrink-0 w-5 h-5 mt-px mr-1 align-middle border rounded-none"
|
|
96
|
+
/>
|
|
97
|
+
<!-- 'key_as_string' exists for dates to have a human readable version -->
|
|
98
|
+
<label
|
|
99
|
+
:for="
|
|
100
|
+
bucket.key_as_string
|
|
101
|
+
? generateId(bucket.key_as_string, groupKey)
|
|
102
|
+
: generateId(bucket.key, groupKey)
|
|
103
|
+
"
|
|
104
|
+
class="form-check-label pl-2 tracking-normal align-middle"
|
|
105
|
+
>
|
|
106
|
+
{{ prettyFilterNames(bucket.key_as_string ? bucket.key_as_string : bucket.key) }}
|
|
107
|
+
<span class="text-gray-mid-dark text-sm">
|
|
108
|
+
({{ bucket.doc_count.toLocaleString() }})
|
|
109
|
+
</span>
|
|
110
|
+
</label>
|
|
111
|
+
</div>
|
|
112
|
+
</template>
|
|
42
113
|
</div>
|
|
43
114
|
</div>
|
|
44
115
|
<div v-else><span class="text-sm text-gray-mid-dark">No matching filters</span></div>
|
|
@@ -63,16 +134,28 @@
|
|
|
63
134
|
</template>
|
|
64
135
|
<script lang="ts">
|
|
65
136
|
// @ts-nocheck
|
|
137
|
+
import { PropType } from 'vue'
|
|
66
138
|
import isEqual from 'lodash/isEqual.js'
|
|
67
139
|
import { mapStores } from 'pinia'
|
|
68
140
|
import { useThemeStore } from '../../store/theme'
|
|
69
141
|
import { lookupContentType } from './../../utils/lookupContentType'
|
|
142
|
+
import { SubFiltersObject } from './../../interfaces'
|
|
143
|
+
import SearchFilterGroupAccordionItem from './../SearchFilterGroupAccordionItem/SearchFilterGroupAccordionItem.vue'
|
|
70
144
|
|
|
71
145
|
export default {
|
|
72
146
|
name: 'SearchFilterGroup',
|
|
147
|
+
components: {
|
|
148
|
+
SearchFilterGroupAccordionItem
|
|
149
|
+
},
|
|
73
150
|
props: {
|
|
74
|
-
filterBy:
|
|
75
|
-
|
|
151
|
+
filterBy: {
|
|
152
|
+
type: Array,
|
|
153
|
+
default: undefined
|
|
154
|
+
},
|
|
155
|
+
buckets: {
|
|
156
|
+
type: Object,
|
|
157
|
+
default: undefined
|
|
158
|
+
},
|
|
76
159
|
hideFilterGroups: {
|
|
77
160
|
type: Array,
|
|
78
161
|
default: () => []
|
|
@@ -83,12 +166,20 @@ export default {
|
|
|
83
166
|
},
|
|
84
167
|
groupTitle: {
|
|
85
168
|
type: String,
|
|
86
|
-
|
|
169
|
+
default: ''
|
|
87
170
|
},
|
|
88
171
|
truncateFilters: {
|
|
89
172
|
type: Boolean,
|
|
90
173
|
required: false,
|
|
91
174
|
default: false
|
|
175
|
+
},
|
|
176
|
+
subFilters: {
|
|
177
|
+
type: Object as PropType<SubFiltersObject>,
|
|
178
|
+
default: undefined
|
|
179
|
+
},
|
|
180
|
+
subFilterAggKey: {
|
|
181
|
+
type: String,
|
|
182
|
+
default: undefined
|
|
92
183
|
}
|
|
93
184
|
},
|
|
94
185
|
emits: ['update:filterBy'],
|
|
@@ -118,7 +209,10 @@ export default {
|
|
|
118
209
|
},
|
|
119
210
|
showFilterGroup() {
|
|
120
211
|
if (this.themeStore.isEdu) {
|
|
121
|
-
return
|
|
212
|
+
return (
|
|
213
|
+
(this.groupTitle || this.buckets?.length) &&
|
|
214
|
+
!this.hideFilterGroups.includes(this.groupKey)
|
|
215
|
+
)
|
|
122
216
|
} else {
|
|
123
217
|
return (
|
|
124
218
|
typeof this.groupKey !== 'undefined' &&
|
|
@@ -127,6 +221,9 @@ export default {
|
|
|
127
221
|
!this.hideFilterGroups.includes(this.groupKey)
|
|
128
222
|
)
|
|
129
223
|
}
|
|
224
|
+
},
|
|
225
|
+
subFilterParentKeys() {
|
|
226
|
+
return this.subFilters ? Object.keys(this.subFilters) : undefined
|
|
130
227
|
}
|
|
131
228
|
},
|
|
132
229
|
watch: {
|
|
@@ -152,11 +249,51 @@ export default {
|
|
|
152
249
|
}
|
|
153
250
|
}
|
|
154
251
|
},
|
|
252
|
+
|
|
155
253
|
methods: {
|
|
156
|
-
generateId(value) {
|
|
254
|
+
generateId(value, group) {
|
|
157
255
|
let valueString = String(value)
|
|
158
256
|
valueString = valueString.split(' ').join('')
|
|
159
|
-
return `filter_${
|
|
257
|
+
return `filter_${group}_${valueString}`
|
|
258
|
+
},
|
|
259
|
+
// used to match sub-filters to their parent
|
|
260
|
+
getSubFilterParentKey(value) {
|
|
261
|
+
let key = value
|
|
262
|
+
if (key) {
|
|
263
|
+
key = key.toLowerCase()
|
|
264
|
+
key = key.replaceAll(' ', '_')
|
|
265
|
+
}
|
|
266
|
+
return key
|
|
267
|
+
},
|
|
268
|
+
hasSubFilters(filterKey) {
|
|
269
|
+
const lookupKey = this.getSubFilterParentKey(filterKey)
|
|
270
|
+
// check if any of the keys are populated in subFilters
|
|
271
|
+
if (this.subFilters && lookupKey && this.subFilters[lookupKey]) {
|
|
272
|
+
return true
|
|
273
|
+
}
|
|
274
|
+
return undefined
|
|
275
|
+
},
|
|
276
|
+
subFilterIsInQueryParams(bucketKey) {
|
|
277
|
+
const subFilters = this.getSubFilters(bucketKey)
|
|
278
|
+
let state = false
|
|
279
|
+
if (subFilters?.length) {
|
|
280
|
+
subFilters.forEach((subFilter) => {
|
|
281
|
+
const agg = subFilter.agg
|
|
282
|
+
const key = subFilter.key
|
|
283
|
+
if (agg && key && this.$route.query[agg]?.includes(key)) {
|
|
284
|
+
state = true
|
|
285
|
+
}
|
|
286
|
+
})
|
|
287
|
+
}
|
|
288
|
+
return state
|
|
289
|
+
},
|
|
290
|
+
getSubFilters(filterKey) {
|
|
291
|
+
const lookupKey = this.getSubFilterParentKey(filterKey)
|
|
292
|
+
// check if any of the keys are populated in subFilters
|
|
293
|
+
if (this.subFilters && lookupKey && this.subFilters[lookupKey]) {
|
|
294
|
+
return this.subFilters[lookupKey]
|
|
295
|
+
}
|
|
296
|
+
return undefined
|
|
160
297
|
},
|
|
161
298
|
toggleShowMoreFilters() {
|
|
162
299
|
if (this.checkbox.checkboxLimit === this.checkbox.initialLimit) {
|
|
@@ -178,6 +315,9 @@ export default {
|
|
|
178
315
|
name = name.replace('EDU ', '')
|
|
179
316
|
}
|
|
180
317
|
return name ? name : key
|
|
318
|
+
},
|
|
319
|
+
getSlotName(key) {
|
|
320
|
+
return `slot_${key}`
|
|
181
321
|
}
|
|
182
322
|
}
|
|
183
323
|
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, reactive, ref } from 'vue'
|
|
3
|
+
import { uniqueId } from 'lodash'
|
|
4
|
+
import IconPlus from './../Icons/IconPlus.vue'
|
|
5
|
+
|
|
6
|
+
export interface SearchFilterGroupAccordionItemProps {
|
|
7
|
+
initOpen?: boolean
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// define props
|
|
11
|
+
const props = withDefaults(defineProps<SearchFilterGroupAccordionItemProps>(), {
|
|
12
|
+
initOpen: false
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
const { initOpen } = reactive(props)
|
|
16
|
+
|
|
17
|
+
const ariaExpanded = ref(false)
|
|
18
|
+
const isHidden = ref(!initOpen)
|
|
19
|
+
const itemId = ref(uniqueId())
|
|
20
|
+
|
|
21
|
+
const panelId = computed(() => {
|
|
22
|
+
return `filterGroup_accordion_panel_${itemId.value}`
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
const headingId = computed(() => {
|
|
26
|
+
return `filterGroup_accordion_heading_${itemId.value}`
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
const handleClick = () => {
|
|
30
|
+
ariaExpanded.value = !ariaExpanded.value
|
|
31
|
+
isHidden.value = !isHidden.value
|
|
32
|
+
if (isHidden.value) {
|
|
33
|
+
emit('filterGroupAccordionItemClosed')
|
|
34
|
+
} else {
|
|
35
|
+
emit('filterGroupAccordionItemOpened')
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const emit = defineEmits(['filterGroupAccordionItemOpened', 'filterGroupAccordionItemClosed'])
|
|
40
|
+
</script>
|
|
41
|
+
<template>
|
|
42
|
+
<div
|
|
43
|
+
class="SearchFilterGroupAccordionItem border-t pt-2.5"
|
|
44
|
+
:class="{
|
|
45
|
+
'-open border-gray-light-mid -mb-px': !isHidden,
|
|
46
|
+
'border-transparent mb-3': isHidden
|
|
47
|
+
}"
|
|
48
|
+
>
|
|
49
|
+
<div class="SearchFilterGroupAccordionItemHeader flex flex-row w-full content-between">
|
|
50
|
+
<template v-if="$slots.header">
|
|
51
|
+
<slot name="header"></slot>
|
|
52
|
+
<button
|
|
53
|
+
v-bind-once="{ id: headingId, 'aria-controls': panelId }"
|
|
54
|
+
:aria-expanded="ariaExpanded"
|
|
55
|
+
aria-label="Expand"
|
|
56
|
+
class="SearchFilterGroupAccordionItem-trigger group block w-auto text-body-lg pl-4 pr-1 -my-2"
|
|
57
|
+
@click="handleClick()"
|
|
58
|
+
>
|
|
59
|
+
<span
|
|
60
|
+
class="SearchFilterGroupAccordionItem-icon inline-block text-xs text-action flex-shrink-0 transform transition-transform"
|
|
61
|
+
:class="{ '!rotate-45': !isHidden }"
|
|
62
|
+
>
|
|
63
|
+
<IconPlus />
|
|
64
|
+
</span>
|
|
65
|
+
</button>
|
|
66
|
+
</template>
|
|
67
|
+
</div>
|
|
68
|
+
<div
|
|
69
|
+
v-show="!isHidden"
|
|
70
|
+
class="SearchFilterGroupAccordionItemContent text-sm"
|
|
71
|
+
>
|
|
72
|
+
<div
|
|
73
|
+
v-bind-once="{ id: panelId, 'aria-labelledby': headingId }"
|
|
74
|
+
role="region"
|
|
75
|
+
class="SearchFilterGroupAccordionItem-panel border-b border-gray-light-mid"
|
|
76
|
+
>
|
|
77
|
+
<slot name="default" />
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
</template>
|
|
82
|
+
<style lang="scss">
|
|
83
|
+
.SearchFilterGroupAccordionItemContent {
|
|
84
|
+
input.mt-px {
|
|
85
|
+
@apply mt-0 #{!important};
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
</style>
|
package/src/interfaces.ts
CHANGED
|
@@ -185,6 +185,27 @@ export interface FooterResponse {
|
|
|
185
185
|
footer: any
|
|
186
186
|
}
|
|
187
187
|
|
|
188
|
+
export interface EDUSubjectArea {
|
|
189
|
+
id: number
|
|
190
|
+
primarySubject: EduResourcesSubject
|
|
191
|
+
subjectArea: string
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export interface SubFiltersObject {
|
|
195
|
+
[key: string]: {
|
|
196
|
+
parentBucketKey: string
|
|
197
|
+
aggParentKey: string
|
|
198
|
+
aggMapKey: string
|
|
199
|
+
subFilterKey: string
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export interface EduSubjectAreasResponse {
|
|
204
|
+
eduSubjectAreas: {
|
|
205
|
+
eduSubjectAreas: EDUSubjectArea[]
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
188
209
|
export type Explorer1Theme = 'defaultTheme' | 'ThemeInternal' | 'ThemeEdu'
|
|
189
210
|
|
|
190
211
|
export interface Attributes {
|
|
@@ -159,7 +159,7 @@
|
|
|
159
159
|
<BaseLink
|
|
160
160
|
variant="none"
|
|
161
161
|
link-class="font-primary text-jpl-red hover:text-jpl-red-light w-full py-3 text-lg"
|
|
162
|
-
to="
|
|
162
|
+
:to="`/missions/?mission_target=${item.target}`"
|
|
163
163
|
>
|
|
164
164
|
{{ item.target }}
|
|
165
165
|
</BaseLink>
|