@explorer-1/vue 0.2.55 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@explorer-1/vue",
3
- "version": "0.2.55",
3
+ "version": "0.2.57",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -30,7 +30,7 @@
30
30
  "vue-bind-once": "^0.2.1",
31
31
  "vue3-compare-image": "^1.2.5",
32
32
  "vue3-observe-visibility": "^1.0.1",
33
- "@explorer-1/common": "1.1.13"
33
+ "@explorer-1/common": "1.1.14"
34
34
  },
35
35
  "devDependencies": {
36
36
  "@vitejs/plugin-vue": "^5.0.4",
@@ -3,13 +3,19 @@ import BlockText, { variants } from './BlockText.vue'
3
3
  export default {
4
4
  title: 'Components/Blocks/BlockText',
5
5
  component: BlockText,
6
+ tags: ['!autodocs'],
6
7
  argTypes: {
7
8
  variant: {
8
9
  control: { type: 'select' },
9
10
  options: Object.keys(variants)
10
11
  },
11
12
  text: { control: { type: 'text' } }
12
- }
13
+ },
14
+ decorators: [
15
+ () => ({
16
+ template: '<div class="lg:w-2/3 mx-auto"><story /></div>'
17
+ })
18
+ ]
13
19
  }
14
20
 
15
21
  export const BaseStory = {
@@ -19,3 +25,26 @@ export const BaseStory = {
19
25
  text: `<p>AVIRIS is the first full spectral range imaging spectrometer and dedicated to <a href="https://en.wikipedia.org/wiki/Remote_sensing" target="_blank">Earth Remote Measurement</a>. Test <a href="#">preventlongurlsfrombreakingoutofcontainerpreventlongurlsfrombreakingoutofcontainerpreventlongurlsfrombreakingoutofcontainerpreventlongurlsfrombreakingoutofcontainer</a>. It is a unique optical sensor that continues to deliver calibrated images of the upwelling spectral radiance in 224 contiguous spectral channels (bands) with wavelengths from 380 to 2510 nanometers. AVIRIS has been flown on four aircraft platforms: NASA's high altitude ER-2 jet, Twin Otter International's turboprop, Scaled Composites' Proteus, and NASA's WB-57. The ER-2 flies at approximately 20 km above sea level, at about 730 km/hr. The Twin Otter aircraft flies at 4km above ground level at 130km/hr. AVIRIS has flown North America including Alaska, Hawaii, Europe, Brazil, and Argentina.</p><p>The objective of the AVIRIS project is to support advanced NASA science and applications research. AVIRIS uses imaging spectroscopy to detect, identify, measure, and monitor constituents and processes of the Earth's surface and atmosphere based on measured constituent absorption and scattering signatures. Science and applications research with AVIRIS data spans a wide range of discipline across the Earth system. </p><hr /><ul><li>Lorem ipsum dolor sit amet consectatur adipscing</li><li>Lorem ipsum dolor sit amet <strong>consectatur adipscing</strong></li><li>Lorem ipsum dolor sit amet consectatur adipscing</li></ul><p>Lorem ipsum dolor sit amet paragraph text</p><ol><li>Lorem ipsum dolor sit amet consectatur adipscing</li><li>Lorem ipsum dolor sit amet consectatur adipscing</li><li>Lorem ipsum dolor <strong>sit amet</strong> consectatur adipscing</li><li>Lorem <strong>ipsum dolor</strong> sit amet consectatur adipscing</li></ol><hr /><p>Lorem ipsum dolor sit amet consectatur adipscing</p>`
20
26
  }
21
27
  }
28
+
29
+ export const RichTextMedia = {
30
+ args: {
31
+ variant: 'large',
32
+ text: `<p data-block-key="5f55p">Description for it.</p><div class="richtext-image fullwidth"><img alt="Perseverance Looks Back at &amp;#x27;Bright Angel&amp;#x27;" height="480" loading="lazy" src="https://picsum.photos/640/480" width="640">
33
+ <div class="richtext-caption">
34
+ <div class="caption">One of the navigation cameras aboard NASAs Perseverance Mars rover captured this view looking back at the Bright Angel area on July 30, 2024.</div>
35
+ <span class="credit">Credit: NASA/JPL-Caltech</span>
36
+ <a class="caption-link" href="#">Full Image Details</a>
37
+ </div>
38
+ </div><p data-block-key="89jcq">More text and another image that&#x27;s full width (above)</p><p data-block-key="6jsp"></p><div class="richtext-image right"><img alt="Carbon Mapper Coalition&amp;#x27;s Tanager Satellite" height="336" loading="lazy" src="https://picsum.photos/640/336" width="640">
39
+ <div class="richtext-caption">
40
+ <div class="caption">This artists concept depicts one of the Carbon Mapper Coalitions Tanager satellites, the first of which launched on Aug. 16, 2024. Tanager-1 will use imaging spectrometer technology developed at JPL to measure greenhouse gas point-source emissions.</div>
41
+ <span class="credit">Credit: Planet Labs PBC</span>
42
+ <a class="caption-link" href="#">Full Image Details</a>
43
+ </div>
44
+ </div><p data-block-key="4409g">More text and something that&#x27;s right-aligned.</p><p data-block-key="bid36"></p><div class="richtext-image left"><img alt="Lecture Brings Galileo&amp;#x27;s Travels into Final Focus" height="350" loading="lazy" src="https://picsum.photos/640/350" width="640">
45
+ <div class="richtext-caption">
46
+ <div class="caption">Lecture Brings Galileo's Travels into Final Focus</div>
47
+ </div>
48
+ </div><p data-block-key="1f4rd">More text and something that&#x27;s left-aligned</p>`
49
+ }
50
+ }
@@ -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
- <!-- correct for zero based index -->
22
- <div
23
- v-if="!truncateFilters || index <= checkbox.checkboxLimit - 1"
24
- class="flex my-2"
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
- <input
27
- :id="bucket.key_as_string ? generateId(bucket.key_as_string) : generateId(bucket.key)"
28
- v-model="filterByHandler"
29
- type="checkbox"
30
- :value="bucket.key_as_string ? bucket.key_as_string : bucket.key"
31
- 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"
32
- />
33
- <!-- 'key_as_string' exists for dates to have a human readable version -->
34
- <label
35
- :for="bucket.key_as_string ? generateId(bucket.key_as_string) : generateId(bucket.key)"
36
- class="form-check-label pl-2 tracking-normal align-middle"
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
- {{ prettyFilterNames(bucket.key_as_string ? bucket.key_as_string : bucket.key) }}
39
- <span class="text-gray-mid-dark"> ({{ bucket.doc_count.toLocaleString() }}) </span>
40
- </label>
41
- </div>
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: Array,
75
- buckets: null,
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
- required: false
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 this.groupTitle && !this.hideFilterGroups.includes(this.groupKey)
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_${this.groupKey}_${valueString}`
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="/missions/?mission_target={{ item.target }}"
162
+ :to="`/missions/?mission_target=${item.target}`"
163
163
  >
164
164
  {{ item.target }}
165
165
  </BaseLink>