@internetarchive/collection-browser 0.3.2-alpha.4 → 0.3.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.
Files changed (218) hide show
  1. package/.editorconfig +29 -29
  2. package/.github/workflows/ci.yml +26 -26
  3. package/.github/workflows/gh-pages-main.yml +39 -39
  4. package/.github/workflows/npm-publish.yml +39 -39
  5. package/.github/workflows/pr-preview.yml +38 -38
  6. package/.husky/pre-commit +4 -4
  7. package/LICENSE +661 -661
  8. package/README.md +83 -83
  9. package/dist/index.d.ts +9 -9
  10. package/dist/index.js +9 -9
  11. package/dist/src/app-root.d.ts +43 -43
  12. package/dist/src/app-root.js +233 -233
  13. package/dist/src/assets/img/icons/arrow-left.d.ts +2 -2
  14. package/dist/src/assets/img/icons/arrow-left.js +2 -2
  15. package/dist/src/assets/img/icons/arrow-right.d.ts +2 -2
  16. package/dist/src/assets/img/icons/arrow-right.js +2 -2
  17. package/dist/src/assets/img/icons/chevron.d.ts +2 -2
  18. package/dist/src/assets/img/icons/chevron.js +2 -2
  19. package/dist/src/assets/img/icons/empty-query.d.ts +2 -2
  20. package/dist/src/assets/img/icons/empty-query.js +2 -2
  21. package/dist/src/assets/img/icons/eye-closed.d.ts +2 -2
  22. package/dist/src/assets/img/icons/eye-closed.js +2 -2
  23. package/dist/src/assets/img/icons/eye.d.ts +2 -2
  24. package/dist/src/assets/img/icons/eye.js +2 -2
  25. package/dist/src/assets/img/icons/favorite-filled.d.ts +1 -1
  26. package/dist/src/assets/img/icons/favorite-filled.js +2 -2
  27. package/dist/src/assets/img/icons/login-required.d.ts +1 -1
  28. package/dist/src/assets/img/icons/login-required.js +2 -2
  29. package/dist/src/assets/img/icons/mediatype/account.d.ts +1 -1
  30. package/dist/src/assets/img/icons/mediatype/account.js +2 -2
  31. package/dist/src/assets/img/icons/mediatype/audio.d.ts +1 -1
  32. package/dist/src/assets/img/icons/mediatype/audio.js +2 -2
  33. package/dist/src/assets/img/icons/mediatype/collection.d.ts +1 -1
  34. package/dist/src/assets/img/icons/mediatype/collection.js +2 -2
  35. package/dist/src/assets/img/icons/mediatype/data.d.ts +1 -1
  36. package/dist/src/assets/img/icons/mediatype/data.js +2 -2
  37. package/dist/src/assets/img/icons/mediatype/etree.d.ts +1 -1
  38. package/dist/src/assets/img/icons/mediatype/etree.js +2 -2
  39. package/dist/src/assets/img/icons/mediatype/film.d.ts +1 -1
  40. package/dist/src/assets/img/icons/mediatype/film.js +2 -2
  41. package/dist/src/assets/img/icons/mediatype/images.d.ts +1 -1
  42. package/dist/src/assets/img/icons/mediatype/images.js +2 -2
  43. package/dist/src/assets/img/icons/mediatype/radio.d.ts +1 -1
  44. package/dist/src/assets/img/icons/mediatype/radio.js +2 -2
  45. package/dist/src/assets/img/icons/mediatype/software.d.ts +1 -1
  46. package/dist/src/assets/img/icons/mediatype/software.js +2 -2
  47. package/dist/src/assets/img/icons/mediatype/texts.d.ts +1 -1
  48. package/dist/src/assets/img/icons/mediatype/texts.js +2 -2
  49. package/dist/src/assets/img/icons/mediatype/tv.d.ts +1 -1
  50. package/dist/src/assets/img/icons/mediatype/tv.js +2 -2
  51. package/dist/src/assets/img/icons/mediatype/video.d.ts +1 -1
  52. package/dist/src/assets/img/icons/mediatype/video.js +2 -2
  53. package/dist/src/assets/img/icons/mediatype/web.d.ts +1 -1
  54. package/dist/src/assets/img/icons/mediatype/web.js +2 -2
  55. package/dist/src/assets/img/icons/null-result.d.ts +2 -2
  56. package/dist/src/assets/img/icons/null-result.js +2 -2
  57. package/dist/src/assets/img/icons/restricted.d.ts +1 -1
  58. package/dist/src/assets/img/icons/restricted.js +2 -2
  59. package/dist/src/assets/img/icons/reviews.d.ts +1 -1
  60. package/dist/src/assets/img/icons/reviews.js +2 -2
  61. package/dist/src/assets/img/icons/upload.d.ts +1 -1
  62. package/dist/src/assets/img/icons/upload.js +2 -2
  63. package/dist/src/assets/img/icons/views.d.ts +1 -1
  64. package/dist/src/assets/img/icons/views.js +2 -2
  65. package/dist/src/circular-activity-indicator.d.ts +5 -5
  66. package/dist/src/circular-activity-indicator.js +17 -17
  67. package/dist/src/collection-browser.d.ts +247 -247
  68. package/dist/src/collection-browser.js +1419 -1418
  69. package/dist/src/collection-browser.js.map +1 -1
  70. package/dist/src/collection-facets/facets-template.d.ts +16 -16
  71. package/dist/src/collection-facets/facets-template.js +125 -125
  72. package/dist/src/collection-facets/more-facets-content.d.ts +74 -74
  73. package/dist/src/collection-facets/more-facets-content.js +330 -330
  74. package/dist/src/collection-facets/more-facets-pagination.d.ts +36 -36
  75. package/dist/src/collection-facets/more-facets-pagination.js +192 -192
  76. package/dist/src/collection-facets.d.ts +73 -73
  77. package/dist/src/collection-facets.js +507 -507
  78. package/dist/src/collection-facets.js.map +1 -1
  79. package/dist/src/empty-placeholder.d.ts +11 -11
  80. package/dist/src/empty-placeholder.js +42 -42
  81. package/dist/src/language-code-handler/language-code-handler.d.ts +37 -37
  82. package/dist/src/language-code-handler/language-code-handler.js +26 -26
  83. package/dist/src/language-code-handler/language-code-mapping.d.ts +1 -1
  84. package/dist/src/language-code-handler/language-code-mapping.js +562 -562
  85. package/dist/src/mediatype/mediatype-config.d.ts +3 -3
  86. package/dist/src/mediatype/mediatype-config.js +85 -85
  87. package/dist/src/models.d.ts +97 -97
  88. package/dist/src/models.js +100 -100
  89. package/dist/src/restoration-state-handler.d.ts +45 -45
  90. package/dist/src/restoration-state-handler.js +220 -220
  91. package/dist/src/sort-filter-bar/alpha-bar.d.ts +9 -9
  92. package/dist/src/sort-filter-bar/alpha-bar.js +41 -41
  93. package/dist/src/sort-filter-bar/img/compact.d.ts +1 -1
  94. package/dist/src/sort-filter-bar/img/compact.js +2 -2
  95. package/dist/src/sort-filter-bar/img/list.d.ts +1 -1
  96. package/dist/src/sort-filter-bar/img/list.js +2 -2
  97. package/dist/src/sort-filter-bar/img/sort-triangle.d.ts +1 -1
  98. package/dist/src/sort-filter-bar/img/sort-triangle.js +2 -2
  99. package/dist/src/sort-filter-bar/img/tile.d.ts +1 -1
  100. package/dist/src/sort-filter-bar/img/tile.js +2 -2
  101. package/dist/src/sort-filter-bar/sort-filter-bar.d.ts +107 -107
  102. package/dist/src/sort-filter-bar/sort-filter-bar.js +423 -423
  103. package/dist/src/styles/item-image-styles.d.ts +8 -8
  104. package/dist/src/styles/item-image-styles.js +9 -9
  105. package/dist/src/tiles/collection-browser-loading-tile.d.ts +5 -5
  106. package/dist/src/tiles/collection-browser-loading-tile.js +15 -15
  107. package/dist/src/tiles/grid/account-tile.d.ts +8 -8
  108. package/dist/src/tiles/grid/account-tile.js +20 -20
  109. package/dist/src/tiles/grid/collection-tile.d.ts +7 -7
  110. package/dist/src/tiles/grid/collection-tile.js +23 -23
  111. package/dist/src/tiles/grid/item-tile.d.ts +24 -24
  112. package/dist/src/tiles/grid/item-tile.js +87 -87
  113. package/dist/src/tiles/grid/tile-stats.d.ts +10 -10
  114. package/dist/src/tiles/grid/tile-stats.js +35 -35
  115. package/dist/src/tiles/image-block.d.ts +17 -17
  116. package/dist/src/tiles/image-block.js +69 -73
  117. package/dist/src/tiles/image-block.js.map +1 -1
  118. package/dist/src/tiles/item-image.d.ts +31 -31
  119. package/dist/src/tiles/item-image.js +103 -103
  120. package/dist/src/tiles/list/account-label.d.ts +1 -1
  121. package/dist/src/tiles/list/account-label.js +6 -6
  122. package/dist/src/tiles/list/date-label.d.ts +1 -1
  123. package/dist/src/tiles/list/date-label.js +12 -12
  124. package/dist/src/tiles/list/tile-list-compact-header.d.ts +12 -12
  125. package/dist/src/tiles/list/tile-list-compact-header.js +41 -41
  126. package/dist/src/tiles/list/tile-list-compact.d.ts +20 -20
  127. package/dist/src/tiles/list/tile-list-compact.js +88 -87
  128. package/dist/src/tiles/list/tile-list-compact.js.map +1 -1
  129. package/dist/src/tiles/list/tile-list.d.ts +50 -50
  130. package/dist/src/tiles/list/tile-list.js +265 -264
  131. package/dist/src/tiles/list/tile-list.js.map +1 -1
  132. package/dist/src/tiles/mediatype-icon.d.ts +9 -9
  133. package/dist/src/tiles/mediatype-icon.js +47 -47
  134. package/dist/src/tiles/overlay/icon-overlay.d.ts +7 -7
  135. package/dist/src/tiles/overlay/icon-overlay.js +30 -31
  136. package/dist/src/tiles/overlay/icon-overlay.js.map +1 -1
  137. package/dist/src/tiles/overlay/text-overlay.d.ts +8 -8
  138. package/dist/src/tiles/overlay/text-overlay.js +31 -31
  139. package/dist/src/tiles/text-snippet-block.d.ts +29 -29
  140. package/dist/src/tiles/text-snippet-block.js +81 -81
  141. package/dist/src/tiles/tile-dispatcher.d.ts +36 -36
  142. package/dist/src/tiles/tile-dispatcher.js +128 -128
  143. package/dist/src/utils/analytics-events.d.ts +18 -18
  144. package/dist/src/utils/analytics-events.js +20 -20
  145. package/dist/src/utils/format-count.d.ts +7 -7
  146. package/dist/src/utils/format-count.js +76 -76
  147. package/dist/src/utils/format-date.d.ts +2 -2
  148. package/dist/src/utils/format-date.js +23 -23
  149. package/dist/test/collection-browser.test.d.ts +1 -1
  150. package/dist/test/collection-browser.test.js +333 -318
  151. package/dist/test/collection-browser.test.js.map +1 -1
  152. package/dist/test/collection-facets/facets-template.test.d.ts +1 -1
  153. package/dist/test/collection-facets/facets-template.test.js +62 -62
  154. package/dist/test/collection-facets/more-facets-content.test.d.ts +1 -1
  155. package/dist/test/collection-facets/more-facets-content.test.js +91 -91
  156. package/dist/test/collection-facets/more-facets-pagination.test.d.ts +1 -1
  157. package/dist/test/collection-facets/more-facets-pagination.test.js +117 -117
  158. package/dist/test/collection-facets.test.d.ts +2 -2
  159. package/dist/test/collection-facets.test.js +423 -423
  160. package/dist/test/collection-facets.test.js.map +1 -1
  161. package/dist/test/empty-placeholder.test.d.ts +1 -1
  162. package/dist/test/empty-placeholder.test.js +33 -33
  163. package/dist/test/icon-overlay.test.d.ts +1 -1
  164. package/dist/test/icon-overlay.test.js +24 -24
  165. package/dist/test/image-block.test.d.ts +1 -0
  166. package/dist/test/image-block.test.js +79 -0
  167. package/dist/test/image-block.test.js.map +1 -0
  168. package/dist/test/item-image.test.d.ts +1 -1
  169. package/dist/test/item-image.test.js +56 -56
  170. package/dist/test/mediatype-config.test.d.ts +1 -1
  171. package/dist/test/mediatype-config.test.js +16 -16
  172. package/dist/test/mocks/mock-analytics-handler.d.ts +10 -10
  173. package/dist/test/mocks/mock-analytics-handler.js +15 -15
  174. package/dist/test/mocks/mock-collection-name-cache.d.ts +7 -7
  175. package/dist/test/mocks/mock-collection-name-cache.js +13 -13
  176. package/dist/test/mocks/mock-search-responses.d.ts +5 -5
  177. package/dist/test/mocks/mock-search-responses.js +103 -103
  178. package/dist/test/mocks/mock-search-service.d.ts +13 -13
  179. package/dist/test/mocks/mock-search-service.js +25 -25
  180. package/dist/test/restoration-state-handler.test.d.ts +1 -1
  181. package/dist/test/restoration-state-handler.test.js +117 -117
  182. package/dist/test/sort-filter-bar/sort-filter-bar.test.d.ts +1 -1
  183. package/dist/test/sort-filter-bar/sort-filter-bar.test.js +113 -113
  184. package/dist/test/text-overlay.test.d.ts +1 -1
  185. package/dist/test/text-overlay.test.js +41 -41
  186. package/dist/test/text-snippet-block.test.d.ts +1 -1
  187. package/dist/test/text-snippet-block.test.js +57 -57
  188. package/dist/test/tile-stats.test.d.ts +1 -1
  189. package/dist/test/tile-stats.test.js +33 -33
  190. package/dist/test/tiles/grid/item-tile.test.d.ts +1 -1
  191. package/dist/test/tiles/grid/item-tile.test.js +107 -107
  192. package/dist/test/tiles/list/tile-list-compact.test.d.ts +1 -0
  193. package/dist/test/tiles/list/tile-list-compact.test.js +31 -0
  194. package/dist/test/tiles/list/tile-list-compact.test.js.map +1 -0
  195. package/dist/test/tiles/list/tile-list.test.d.ts +1 -1
  196. package/dist/test/tiles/list/tile-list.test.js +36 -36
  197. package/dist/test/utils/format-count.test.d.ts +1 -1
  198. package/dist/test/utils/format-count.test.js +23 -23
  199. package/dist/test/utils/format-date.test.d.ts +1 -1
  200. package/dist/test/utils/format-date.test.js +17 -17
  201. package/index.html +24 -24
  202. package/local.archive.org.cert +86 -86
  203. package/local.archive.org.key +27 -27
  204. package/package.json +115 -115
  205. package/renovate.json +6 -6
  206. package/src/collection-browser.ts +1547 -1547
  207. package/src/collection-facets.ts +582 -582
  208. package/src/tiles/image-block.ts +7 -10
  209. package/src/tiles/list/tile-list-compact.ts +1 -0
  210. package/src/tiles/list/tile-list.ts +1 -0
  211. package/src/tiles/overlay/icon-overlay.ts +3 -4
  212. package/test/collection-browser.test.ts +24 -1
  213. package/test/collection-facets.test.ts +556 -556
  214. package/test/image-block.test.ts +86 -0
  215. package/test/tiles/list/tile-list-compact.test.ts +38 -0
  216. package/tsconfig.json +21 -21
  217. package/web-dev-server.config.mjs +30 -30
  218. package/web-test-runner.config.mjs +41 -41
@@ -1,582 +1,582 @@
1
- /* eslint-disable import/no-duplicates */
2
- import {
3
- css,
4
- html,
5
- LitElement,
6
- PropertyValues,
7
- nothing,
8
- TemplateResult,
9
- } from 'lit';
10
- import { customElement, property, state } from 'lit/decorators.js';
11
- import {
12
- Aggregation,
13
- Bucket,
14
- SearchServiceInterface,
15
- SearchType,
16
- } from '@internetarchive/search-service';
17
- import '@internetarchive/histogram-date-range';
18
- import '@internetarchive/feature-feedback';
19
- import '@internetarchive/collection-name-cache';
20
- import type { CollectionNameCacheInterface } from '@internetarchive/collection-name-cache';
21
- import {
22
- ModalConfig,
23
- ModalManagerInterface,
24
- } from '@internetarchive/modal-manager';
25
- import chevronIcon from './assets/img/icons/chevron';
26
- import {
27
- FacetOption,
28
- SelectedFacets,
29
- FacetGroup,
30
- FacetBucket,
31
- facetDisplayOrder,
32
- facetTitles,
33
- lendingFacetDisplayNames,
34
- lendingFacetKeysVisibility,
35
- LendingFacetKey,
36
- } from './models';
37
- import type { LanguageCodeHandlerInterface } from './language-code-handler/language-code-handler';
38
- import './collection-facets/more-facets-content';
39
- import './collection-facets/facets-template';
40
-
41
- @customElement('collection-facets')
42
- export class CollectionFacets extends LitElement {
43
- @property({ type: Object }) searchService?: SearchServiceInterface;
44
-
45
- @property({ type: String }) searchType?: SearchType;
46
-
47
- @property({ type: Object }) aggregations?: Record<string, Aggregation>;
48
-
49
- @property({ type: Object }) fullYearsHistogramAggregation?: Aggregation;
50
-
51
- @property({ type: Number }) previousSearchType?: SearchType;
52
-
53
- @property({ type: String }) minSelectedDate?: string;
54
-
55
- @property({ type: String }) maxSelectedDate?: string;
56
-
57
- @property({ type: Boolean }) facetsLoading = false;
58
-
59
- @property({ type: Boolean }) fullYearAggregationLoading = false;
60
-
61
- @property({ type: Object }) selectedFacets?: SelectedFacets;
62
-
63
- @property({ type: Boolean }) collapsableFacets = false;
64
-
65
- @property({ type: Boolean }) showHistogramDatePicker = false;
66
-
67
- @property({ type: String }) fullQuery?: string;
68
-
69
- @property({ type: Object }) modalManager?: ModalManagerInterface;
70
-
71
- @property({ type: Object })
72
- languageCodeHandler?: LanguageCodeHandlerInterface;
73
-
74
- @property({ type: Object })
75
- collectionNameCache?: CollectionNameCacheInterface;
76
-
77
- /** Fires when a facet is clicked */
78
- @property({ type: Function }) onFacetClick?: (
79
- name: FacetOption,
80
- facetChecked: boolean,
81
- negative: boolean
82
- ) => void;
83
-
84
- @state() openFacets: Record<FacetOption, boolean> = {
85
- subject: false,
86
- lending: false,
87
- mediatype: false,
88
- language: false,
89
- creator: false,
90
- collection: false,
91
- year: false,
92
- };
93
-
94
- @property({ type: Object, attribute: false })
95
-
96
- /**
97
- * render number of facet items
98
- */
99
- private allowedFacetCount = 6;
100
-
101
- render() {
102
- return html`
103
- <div id="container" class="${this.facetsLoading ? 'loading' : ''}">
104
- ${this.showHistogramDatePicker && this.fullYearsHistogramAggregation
105
- ? html`
106
- <div class="facet-group">
107
- <h1>Year Published <feature-feedback></feature-feedback></h1>
108
- ${this.histogramTemplate}
109
- </div>
110
- `
111
- : nothing}
112
- ${this.mergedFacets.map(facetGroup =>
113
- this.getFacetGroupTemplate(facetGroup)
114
- )}
115
- </div>
116
- `;
117
- }
118
-
119
- updated(changed: PropertyValues) {
120
- if (changed.has('selectedFacets')) {
121
- this.dispatchFacetsChangedEvent();
122
- }
123
- }
124
-
125
- // TODO: want to fire analytics?
126
- private dispatchFacetsChangedEvent() {
127
- const event = new CustomEvent<SelectedFacets>('facetsChanged', {
128
- detail: this.selectedFacets,
129
- });
130
- this.dispatchEvent(event);
131
- }
132
-
133
- private get currentYearsHistogramAggregation(): Aggregation | undefined {
134
- return this.aggregations?.year_histogram;
135
- }
136
-
137
- private get histogramTemplate() {
138
- const { fullYearsHistogramAggregation } = this;
139
- return html`
140
- <histogram-date-range
141
- .minDate=${fullYearsHistogramAggregation?.first_bucket_key}
142
- .maxDate=${fullYearsHistogramAggregation?.last_bucket_key}
143
- .minSelectedDate=${this.minSelectedDate}
144
- .maxSelectedDate=${this.maxSelectedDate}
145
- .updateDelay=${100}
146
- missingDataMessage="..."
147
- .width=${180}
148
- .bins=${fullYearsHistogramAggregation?.buckets as number[]}
149
- @histogramDateRangeUpdated=${this.histogramDateRangeUpdated}
150
- ></histogram-date-range>
151
- `;
152
- }
153
-
154
- private histogramDateRangeUpdated(
155
- e: CustomEvent<{
156
- minDate: string;
157
- maxDate: string;
158
- }>
159
- ) {
160
- const { minDate, maxDate } = e.detail;
161
- const event = new CustomEvent('histogramDateRangeUpdated', {
162
- detail: { minDate, maxDate },
163
- });
164
- this.dispatchEvent(event);
165
- }
166
-
167
- /**
168
- * Combines the selected facets with the aggregations to create a single list of facets
169
- */
170
- private get mergedFacets(): FacetGroup[] {
171
- const facetGroups: FacetGroup[] = [];
172
-
173
- facetDisplayOrder.forEach(facetKey => {
174
- const selectedFacetGroup = this.selectedFacetGroups.find(
175
- group => group.key === facetKey
176
- );
177
- const aggregateFacetGroup = this.aggregationFacetGroups.find(
178
- group => group.key === facetKey
179
- );
180
-
181
- // if the user selected a facet, but it's not in the aggregation, we add it as-is
182
- if (selectedFacetGroup && !aggregateFacetGroup) {
183
- facetGroups.push(selectedFacetGroup);
184
- return;
185
- }
186
-
187
- // if we don't have an aggregate facet group, don't add this to the list
188
- if (!aggregateFacetGroup) return;
189
-
190
- // start with either the selected group if we have one, or the aggregate group
191
- const facetGroup = selectedFacetGroup ?? aggregateFacetGroup;
192
-
193
- // attach the counts to the selected buckets
194
- let bucketsWithCount =
195
- selectedFacetGroup?.buckets.map(bucket => {
196
- const selectedBucket = aggregateFacetGroup.buckets.find(
197
- b => b.key === bucket.key
198
- );
199
- return selectedBucket
200
- ? {
201
- ...bucket,
202
- count: selectedBucket.count,
203
- }
204
- : bucket;
205
- }) ?? [];
206
-
207
- // append any additional buckets that were not selected
208
- aggregateFacetGroup.buckets.forEach(bucket => {
209
- const existingBucket = bucketsWithCount.find(b => b.key === bucket.key);
210
- if (existingBucket) return;
211
- bucketsWithCount.push(bucket);
212
- });
213
-
214
- // For lending facets, only include a specific subset of buckets
215
- if (facetKey === 'lending') {
216
- bucketsWithCount = bucketsWithCount.filter(
217
- bucket => lendingFacetKeysVisibility[bucket.key as LendingFacetKey]
218
- );
219
- }
220
-
221
- /**
222
- * render limited facet items on page facet area
223
- *
224
- * - by-default we are showing 6 items
225
- * - additionally want to show all items (selected/suppressed) in page facet area
226
- */
227
- let allowedFacetCount = Object.keys(
228
- (selectedFacetGroup?.buckets as []) || []
229
- )?.length;
230
- if (allowedFacetCount < this.allowedFacetCount) {
231
- allowedFacetCount = this.allowedFacetCount; // splice start index from 0th
232
- }
233
-
234
- // splice how many items we want to show in page facet area
235
- facetGroup.buckets = bucketsWithCount.splice(0, allowedFacetCount);
236
-
237
- facetGroups.push(facetGroup);
238
- });
239
-
240
- return facetGroups;
241
- }
242
-
243
- /**
244
- * Converts the selected facets to a `FacetGroup` array,
245
- * which is easier to work with
246
- */
247
- private get selectedFacetGroups(): FacetGroup[] {
248
- if (!this.selectedFacets) return [];
249
-
250
- const facetGroups: FacetGroup[] = Object.entries(this.selectedFacets).map(
251
- ([key, selectedFacets]) => {
252
- const option = key as FacetOption;
253
- const title = facetTitles[option];
254
-
255
- const buckets: FacetBucket[] = Object.entries(selectedFacets).map(
256
- ([value, facetData]) => {
257
- let displayText = value;
258
- // for selected languages, we store the language code instead of the
259
- // display name, so look up the name from the mapping
260
- if (option === 'language') {
261
- displayText =
262
- this.languageCodeHandler?.getLanguageNameFromCodeString(
263
- value
264
- ) ?? value;
265
- }
266
- // for lending facets, convert the key to a readable format
267
- if (option === 'lending') {
268
- displayText =
269
- lendingFacetDisplayNames[value as LendingFacetKey] ?? value;
270
- }
271
- return {
272
- displayText,
273
- key: value,
274
- count: facetData.count,
275
- state: facetData.state,
276
- };
277
- }
278
- );
279
-
280
- return {
281
- title,
282
- key: option,
283
- buckets,
284
- };
285
- }
286
- );
287
-
288
- return facetGroups;
289
- }
290
-
291
- /**
292
- * Converts the raw `aggregations` to `FacetGroups`, which are easier to use
293
- */
294
- private get aggregationFacetGroups(): FacetGroup[] {
295
- const facetGroups: FacetGroup[] = [];
296
- Object.entries(this.aggregations ?? []).forEach(([key, buckets]) => {
297
- // the year_histogram data is in a different format so can't be handled here
298
- if (key === 'year_histogram') return;
299
-
300
- const option = key as FacetOption;
301
- const title = facetTitles[option];
302
- if (!title) return;
303
-
304
- const castedBuckets = buckets.buckets as Bucket[];
305
-
306
- // we are not showing fav- items in facets
307
- castedBuckets?.filter(
308
- bucket => bucket?.key?.toString()?.startsWith('fav-') === false
309
- );
310
-
311
- const facetBuckets: FacetBucket[] = castedBuckets.map(bucket => {
312
- let bucketKey = bucket.key;
313
- let displayText = `${bucket.key}`;
314
- // for languages, we need to search by language code instead of the
315
- // display name, which is what we get from the search engine result
316
- if (option === 'language') {
317
- // const languageCodeKey = languageToCodeMap[bucket.key];
318
- bucketKey =
319
- this.languageCodeHandler?.getCodeStringFromLanguageName(
320
- `${bucket.key}`
321
- ) ?? bucket.key;
322
- // bucketKey = languageCodeKey ?? bucket.key;
323
- }
324
- // for lending facets, convert the bucket key to a readable format
325
- if (option === 'lending') {
326
- displayText =
327
- lendingFacetDisplayNames[bucket.key as LendingFacetKey] ??
328
- `${bucket.key}`;
329
- }
330
- return {
331
- displayText,
332
- key: `${bucketKey}`,
333
- count: bucket.doc_count,
334
- state: 'none',
335
- };
336
- });
337
- const group: FacetGroup = {
338
- title,
339
- key: option,
340
- buckets: facetBuckets,
341
- };
342
- facetGroups.push(group);
343
- });
344
- return facetGroups;
345
- }
346
-
347
- /**
348
- * Generate the template for a facet group with a header and the collapsible
349
- * chevron for the mobile view
350
- */
351
- private getFacetGroupTemplate(
352
- facetGroup: FacetGroup
353
- ): TemplateResult | typeof nothing {
354
- if (facetGroup.buckets.length === 0) return nothing;
355
- const { key } = facetGroup;
356
- const isOpen = this.openFacets[key];
357
- const collapser = html`
358
- <span class="collapser ${isOpen ? 'open' : ''}"> ${chevronIcon} </span>
359
- `;
360
-
361
- return html`
362
- <div class="facet-group ${this.collapsableFacets ? 'mobile' : ''}">
363
- <div class="facet-group-header">
364
- <h1
365
- @click=${() => {
366
- const newOpenFacets = { ...this.openFacets };
367
- newOpenFacets[key] = !isOpen;
368
- this.openFacets = newOpenFacets;
369
- }}
370
- @keyup=${() => {
371
- const newOpenFacets = { ...this.openFacets };
372
- newOpenFacets[key] = !isOpen;
373
- this.openFacets = newOpenFacets;
374
- }}
375
- >
376
- ${this.collapsableFacets ? collapser : nothing} ${facetGroup.title}
377
- </h1>
378
- ${this.moreFacetsSortingIcon(facetGroup)}
379
- </div>
380
- <div class="facet-group-content ${isOpen ? 'open' : ''}">
381
- ${this.getFacetTemplate(facetGroup)}
382
- ${this.searchMoreFacetsLink(facetGroup)}
383
- </div>
384
- </div>
385
- `;
386
- }
387
-
388
- private moreFacetsSortingIcon(
389
- facetGroup: FacetGroup
390
- ): TemplateResult | typeof nothing {
391
- // Display the sorting icon for every facet group except lending
392
- return facetGroup.key === 'lending'
393
- ? nothing
394
- : html`
395
- <input
396
- class="sorting-icon"
397
- type="image"
398
- @click=${() => this.showMoreFacetsModal(facetGroup, 'alpha')}
399
- src="https://archive.org/images/filter-count.png"
400
- alt="Sort alphabetically"
401
- />
402
- `;
403
- }
404
-
405
- /**
406
- * Generate the More... link button just below the facets group
407
- *
408
- * TODO: want to fire analytics?
409
- */
410
- private searchMoreFacetsLink(
411
- facetGroup: FacetGroup
412
- ): TemplateResult | typeof nothing {
413
- // Don't render More... links for FTS searches
414
- if (this.previousSearchType === SearchType.FULLTEXT) {
415
- return nothing;
416
- }
417
-
418
- // Don't render More... links for lending facets
419
- if (facetGroup.key === 'lending') {
420
- return nothing;
421
- }
422
-
423
- // Don't render More... link if the number of facets < this.allowedFacetCount
424
- if (Object.keys(facetGroup.buckets).length < this.allowedFacetCount) {
425
- return nothing;
426
- }
427
-
428
- return html`<button
429
- class="more-link"
430
- @click=${() => {
431
- this.showMoreFacetsModal(facetGroup, 'count');
432
- this.dispatchEvent(
433
- new CustomEvent('showMoreFacets', { detail: facetGroup.key })
434
- );
435
- }}
436
- >
437
- More...
438
- </button>`;
439
- }
440
-
441
- async showMoreFacetsModal(
442
- facetGroup: FacetGroup,
443
- sortedBy: string
444
- ): Promise<void> {
445
- const facetAggrKey = facetGroup.key;
446
-
447
- const customModalContent = html`
448
- <more-facets-content
449
- .facetKey=${facetGroup.key}
450
- .facetAggregationKey=${facetAggrKey}
451
- .fullQuery=${this.fullQuery}
452
- .modalManager=${this.modalManager}
453
- .searchService=${this.searchService}
454
- .searchType=${this.searchType}
455
- .collectionNameCache=${this.collectionNameCache}
456
- .languageCodeHandler=${this.languageCodeHandler}
457
- .selectedFacets=${this.selectedFacets}
458
- .sortedBy=${sortedBy}
459
- @facetsChanged=${(e: CustomEvent) => {
460
- const event = new CustomEvent<SelectedFacets>('facetsChanged', {
461
- detail: e.detail,
462
- bubbles: true,
463
- composed: true,
464
- });
465
- this.dispatchEvent(event);
466
- }}
467
- >
468
- </more-facets-content>
469
- `;
470
-
471
- const config = new ModalConfig({
472
- bodyColor: '#fff',
473
- headerColor: '#194880',
474
- showHeaderLogo: false,
475
- closeOnBackdropClick: true, // TODO: want to fire analytics
476
- title: html`Select filters`,
477
- });
478
- this.modalManager?.classList.add('more-search-facets');
479
- this.modalManager?.showModal({
480
- config,
481
- customModalContent,
482
- });
483
- }
484
-
485
- /**
486
- * Generate the list template for each bucket in a facet group
487
- */
488
- private getFacetTemplate(facetGroup: FacetGroup): TemplateResult {
489
- return html`
490
- <facets-template
491
- .facetGroup=${facetGroup}
492
- .selectedFacets=${this.selectedFacets}
493
- .renderOn=${'page'}
494
- .collectionNameCache=${this.collectionNameCache}
495
- @selectedFacetsChanged=${(e: CustomEvent) => {
496
- const event = new CustomEvent<SelectedFacets>('facetsChanged', {
497
- detail: e.detail,
498
- bubbles: true,
499
- composed: true,
500
- });
501
- this.dispatchEvent(event);
502
- }}
503
- ></facets-template>
504
- `;
505
- }
506
-
507
- static get styles() {
508
- return css`
509
- #container.loading {
510
- opacity: 0.5;
511
- }
512
-
513
- .collapser {
514
- display: inline-block;
515
- cursor: pointer;
516
- width: 10px;
517
- height: 10px;
518
- }
519
-
520
- .collapser svg {
521
- transition: transform 0.2s ease-in-out;
522
- }
523
-
524
- .collapser.open svg {
525
- transform: rotate(90deg);
526
- }
527
-
528
- .facet-group {
529
- margin-bottom: 2rem;
530
- }
531
-
532
- .facet-group h1 {
533
- margin-bottom: 0.7rem;
534
- }
535
-
536
- .facet-group.mobile h1 {
537
- cursor: pointer;
538
- }
539
-
540
- .facet-group-header {
541
- display: flex;
542
- margin-bottom: 0.7rem;
543
- justify-content: space-between;
544
- border-bottom: 1px solid rgb(232, 232, 232);
545
- }
546
-
547
- .facet-group-content {
548
- transition: max-height 0.2s ease-in-out;
549
- }
550
-
551
- .facet-group.mobile .facet-group-content {
552
- max-height: 0;
553
- overflow: hidden;
554
- }
555
-
556
- .facet-group.mobile .facet-group-content.open {
557
- max-height: 2000px;
558
- }
559
-
560
- h1 {
561
- font-size: 1.4rem;
562
- font-weight: 200
563
- padding-bottom: 3px;
564
- margin: 0;
565
- }
566
-
567
- .more-link {
568
- font-size: 1.2rem;
569
- text-decoration: none;
570
- padding: 0;
571
- background: inherit;
572
- border: 0;
573
- color: blue;
574
- cursor: pointer;
575
- }
576
- .sorting-icon {
577
- height: 15px;
578
- cursor: pointer;
579
- }
580
- `;
581
- }
582
- }
1
+ /* eslint-disable import/no-duplicates */
2
+ import {
3
+ css,
4
+ html,
5
+ LitElement,
6
+ PropertyValues,
7
+ nothing,
8
+ TemplateResult,
9
+ } from 'lit';
10
+ import { customElement, property, state } from 'lit/decorators.js';
11
+ import type {
12
+ Aggregation,
13
+ Bucket,
14
+ SearchServiceInterface,
15
+ SearchType,
16
+ } from '@internetarchive/search-service';
17
+ import '@internetarchive/histogram-date-range';
18
+ import '@internetarchive/feature-feedback';
19
+ import '@internetarchive/collection-name-cache';
20
+ import type { CollectionNameCacheInterface } from '@internetarchive/collection-name-cache';
21
+ import {
22
+ ModalConfig,
23
+ ModalManagerInterface,
24
+ } from '@internetarchive/modal-manager';
25
+ import chevronIcon from './assets/img/icons/chevron';
26
+ import {
27
+ FacetOption,
28
+ SelectedFacets,
29
+ FacetGroup,
30
+ FacetBucket,
31
+ facetDisplayOrder,
32
+ facetTitles,
33
+ lendingFacetDisplayNames,
34
+ lendingFacetKeysVisibility,
35
+ LendingFacetKey,
36
+ } from './models';
37
+ import type { LanguageCodeHandlerInterface } from './language-code-handler/language-code-handler';
38
+ import './collection-facets/more-facets-content';
39
+ import './collection-facets/facets-template';
40
+
41
+ @customElement('collection-facets')
42
+ export class CollectionFacets extends LitElement {
43
+ @property({ type: Object }) searchService?: SearchServiceInterface;
44
+
45
+ @property({ type: String }) searchType?: SearchType;
46
+
47
+ @property({ type: Object }) aggregations?: Record<string, Aggregation>;
48
+
49
+ @property({ type: Object }) fullYearsHistogramAggregation?: Aggregation;
50
+
51
+ @property({ type: String }) minSelectedDate?: string;
52
+
53
+ @property({ type: String }) maxSelectedDate?: string;
54
+
55
+ @property({ type: Boolean }) moreLinksVisible = true;
56
+
57
+ @property({ type: Boolean }) facetsLoading = false;
58
+
59
+ @property({ type: Boolean }) fullYearAggregationLoading = false;
60
+
61
+ @property({ type: Object }) selectedFacets?: SelectedFacets;
62
+
63
+ @property({ type: Boolean }) collapsableFacets = false;
64
+
65
+ @property({ type: Boolean }) showHistogramDatePicker = false;
66
+
67
+ @property({ type: String }) fullQuery?: string;
68
+
69
+ @property({ type: Object }) modalManager?: ModalManagerInterface;
70
+
71
+ @property({ type: Object })
72
+ languageCodeHandler?: LanguageCodeHandlerInterface;
73
+
74
+ @property({ type: Object })
75
+ collectionNameCache?: CollectionNameCacheInterface;
76
+
77
+ /** Fires when a facet is clicked */
78
+ @property({ type: Function }) onFacetClick?: (
79
+ name: FacetOption,
80
+ facetChecked: boolean,
81
+ negative: boolean
82
+ ) => void;
83
+
84
+ @state() openFacets: Record<FacetOption, boolean> = {
85
+ subject: false,
86
+ lending: false,
87
+ mediatype: false,
88
+ language: false,
89
+ creator: false,
90
+ collection: false,
91
+ year: false,
92
+ };
93
+
94
+ @property({ type: Object, attribute: false })
95
+
96
+ /**
97
+ * render number of facet items
98
+ */
99
+ private allowedFacetCount = 6;
100
+
101
+ render() {
102
+ return html`
103
+ <div id="container" class="${this.facetsLoading ? 'loading' : ''}">
104
+ ${this.showHistogramDatePicker && this.fullYearsHistogramAggregation
105
+ ? html`
106
+ <div class="facet-group">
107
+ <h1>Year Published <feature-feedback></feature-feedback></h1>
108
+ ${this.histogramTemplate}
109
+ </div>
110
+ `
111
+ : nothing}
112
+ ${this.mergedFacets.map(facetGroup =>
113
+ this.getFacetGroupTemplate(facetGroup)
114
+ )}
115
+ </div>
116
+ `;
117
+ }
118
+
119
+ updated(changed: PropertyValues) {
120
+ if (changed.has('selectedFacets')) {
121
+ this.dispatchFacetsChangedEvent();
122
+ }
123
+ }
124
+
125
+ // TODO: want to fire analytics?
126
+ private dispatchFacetsChangedEvent() {
127
+ const event = new CustomEvent<SelectedFacets>('facetsChanged', {
128
+ detail: this.selectedFacets,
129
+ });
130
+ this.dispatchEvent(event);
131
+ }
132
+
133
+ private get currentYearsHistogramAggregation(): Aggregation | undefined {
134
+ return this.aggregations?.year_histogram;
135
+ }
136
+
137
+ private get histogramTemplate() {
138
+ const { fullYearsHistogramAggregation } = this;
139
+ return html`
140
+ <histogram-date-range
141
+ .minDate=${fullYearsHistogramAggregation?.first_bucket_key}
142
+ .maxDate=${fullYearsHistogramAggregation?.last_bucket_key}
143
+ .minSelectedDate=${this.minSelectedDate}
144
+ .maxSelectedDate=${this.maxSelectedDate}
145
+ .updateDelay=${100}
146
+ missingDataMessage="..."
147
+ .width=${180}
148
+ .bins=${fullYearsHistogramAggregation?.buckets as number[]}
149
+ @histogramDateRangeUpdated=${this.histogramDateRangeUpdated}
150
+ ></histogram-date-range>
151
+ `;
152
+ }
153
+
154
+ private histogramDateRangeUpdated(
155
+ e: CustomEvent<{
156
+ minDate: string;
157
+ maxDate: string;
158
+ }>
159
+ ) {
160
+ const { minDate, maxDate } = e.detail;
161
+ const event = new CustomEvent('histogramDateRangeUpdated', {
162
+ detail: { minDate, maxDate },
163
+ });
164
+ this.dispatchEvent(event);
165
+ }
166
+
167
+ /**
168
+ * Combines the selected facets with the aggregations to create a single list of facets
169
+ */
170
+ private get mergedFacets(): FacetGroup[] {
171
+ const facetGroups: FacetGroup[] = [];
172
+
173
+ facetDisplayOrder.forEach(facetKey => {
174
+ const selectedFacetGroup = this.selectedFacetGroups.find(
175
+ group => group.key === facetKey
176
+ );
177
+ const aggregateFacetGroup = this.aggregationFacetGroups.find(
178
+ group => group.key === facetKey
179
+ );
180
+
181
+ // if the user selected a facet, but it's not in the aggregation, we add it as-is
182
+ if (selectedFacetGroup && !aggregateFacetGroup) {
183
+ facetGroups.push(selectedFacetGroup);
184
+ return;
185
+ }
186
+
187
+ // if we don't have an aggregate facet group, don't add this to the list
188
+ if (!aggregateFacetGroup) return;
189
+
190
+ // start with either the selected group if we have one, or the aggregate group
191
+ const facetGroup = selectedFacetGroup ?? aggregateFacetGroup;
192
+
193
+ // attach the counts to the selected buckets
194
+ let bucketsWithCount =
195
+ selectedFacetGroup?.buckets.map(bucket => {
196
+ const selectedBucket = aggregateFacetGroup.buckets.find(
197
+ b => b.key === bucket.key
198
+ );
199
+ return selectedBucket
200
+ ? {
201
+ ...bucket,
202
+ count: selectedBucket.count,
203
+ }
204
+ : bucket;
205
+ }) ?? [];
206
+
207
+ // append any additional buckets that were not selected
208
+ aggregateFacetGroup.buckets.forEach(bucket => {
209
+ const existingBucket = bucketsWithCount.find(b => b.key === bucket.key);
210
+ if (existingBucket) return;
211
+ bucketsWithCount.push(bucket);
212
+ });
213
+
214
+ // For lending facets, only include a specific subset of buckets
215
+ if (facetKey === 'lending') {
216
+ bucketsWithCount = bucketsWithCount.filter(
217
+ bucket => lendingFacetKeysVisibility[bucket.key as LendingFacetKey]
218
+ );
219
+ }
220
+
221
+ /**
222
+ * render limited facet items on page facet area
223
+ *
224
+ * - by-default we are showing 6 items
225
+ * - additionally want to show all items (selected/suppressed) in page facet area
226
+ */
227
+ let allowedFacetCount = Object.keys(
228
+ (selectedFacetGroup?.buckets as []) || []
229
+ )?.length;
230
+ if (allowedFacetCount < this.allowedFacetCount) {
231
+ allowedFacetCount = this.allowedFacetCount; // splice start index from 0th
232
+ }
233
+
234
+ // splice how many items we want to show in page facet area
235
+ facetGroup.buckets = bucketsWithCount.splice(0, allowedFacetCount);
236
+
237
+ facetGroups.push(facetGroup);
238
+ });
239
+
240
+ return facetGroups;
241
+ }
242
+
243
+ /**
244
+ * Converts the selected facets to a `FacetGroup` array,
245
+ * which is easier to work with
246
+ */
247
+ private get selectedFacetGroups(): FacetGroup[] {
248
+ if (!this.selectedFacets) return [];
249
+
250
+ const facetGroups: FacetGroup[] = Object.entries(this.selectedFacets).map(
251
+ ([key, selectedFacets]) => {
252
+ const option = key as FacetOption;
253
+ const title = facetTitles[option];
254
+
255
+ const buckets: FacetBucket[] = Object.entries(selectedFacets).map(
256
+ ([value, facetData]) => {
257
+ let displayText = value;
258
+ // for selected languages, we store the language code instead of the
259
+ // display name, so look up the name from the mapping
260
+ if (option === 'language') {
261
+ displayText =
262
+ this.languageCodeHandler?.getLanguageNameFromCodeString(
263
+ value
264
+ ) ?? value;
265
+ }
266
+ // for lending facets, convert the key to a readable format
267
+ if (option === 'lending') {
268
+ displayText =
269
+ lendingFacetDisplayNames[value as LendingFacetKey] ?? value;
270
+ }
271
+ return {
272
+ displayText,
273
+ key: value,
274
+ count: facetData.count,
275
+ state: facetData.state,
276
+ };
277
+ }
278
+ );
279
+
280
+ return {
281
+ title,
282
+ key: option,
283
+ buckets,
284
+ };
285
+ }
286
+ );
287
+
288
+ return facetGroups;
289
+ }
290
+
291
+ /**
292
+ * Converts the raw `aggregations` to `FacetGroups`, which are easier to use
293
+ */
294
+ private get aggregationFacetGroups(): FacetGroup[] {
295
+ const facetGroups: FacetGroup[] = [];
296
+ Object.entries(this.aggregations ?? []).forEach(([key, buckets]) => {
297
+ // the year_histogram data is in a different format so can't be handled here
298
+ if (key === 'year_histogram') return;
299
+
300
+ const option = key as FacetOption;
301
+ const title = facetTitles[option];
302
+ if (!title) return;
303
+
304
+ const castedBuckets = buckets.buckets as Bucket[];
305
+
306
+ // we are not showing fav- items in facets
307
+ castedBuckets?.filter(
308
+ bucket => bucket?.key?.toString()?.startsWith('fav-') === false
309
+ );
310
+
311
+ const facetBuckets: FacetBucket[] = castedBuckets.map(bucket => {
312
+ let bucketKey = bucket.key;
313
+ let displayText = `${bucket.key}`;
314
+ // for languages, we need to search by language code instead of the
315
+ // display name, which is what we get from the search engine result
316
+ if (option === 'language') {
317
+ // const languageCodeKey = languageToCodeMap[bucket.key];
318
+ bucketKey =
319
+ this.languageCodeHandler?.getCodeStringFromLanguageName(
320
+ `${bucket.key}`
321
+ ) ?? bucket.key;
322
+ // bucketKey = languageCodeKey ?? bucket.key;
323
+ }
324
+ // for lending facets, convert the bucket key to a readable format
325
+ if (option === 'lending') {
326
+ displayText =
327
+ lendingFacetDisplayNames[bucket.key as LendingFacetKey] ??
328
+ `${bucket.key}`;
329
+ }
330
+ return {
331
+ displayText,
332
+ key: `${bucketKey}`,
333
+ count: bucket.doc_count,
334
+ state: 'none',
335
+ };
336
+ });
337
+ const group: FacetGroup = {
338
+ title,
339
+ key: option,
340
+ buckets: facetBuckets,
341
+ };
342
+ facetGroups.push(group);
343
+ });
344
+ return facetGroups;
345
+ }
346
+
347
+ /**
348
+ * Generate the template for a facet group with a header and the collapsible
349
+ * chevron for the mobile view
350
+ */
351
+ private getFacetGroupTemplate(
352
+ facetGroup: FacetGroup
353
+ ): TemplateResult | typeof nothing {
354
+ if (facetGroup.buckets.length === 0) return nothing;
355
+ const { key } = facetGroup;
356
+ const isOpen = this.openFacets[key];
357
+ const collapser = html`
358
+ <span class="collapser ${isOpen ? 'open' : ''}"> ${chevronIcon} </span>
359
+ `;
360
+
361
+ return html`
362
+ <div class="facet-group ${this.collapsableFacets ? 'mobile' : ''}">
363
+ <div class="facet-group-header">
364
+ <h1
365
+ @click=${() => {
366
+ const newOpenFacets = { ...this.openFacets };
367
+ newOpenFacets[key] = !isOpen;
368
+ this.openFacets = newOpenFacets;
369
+ }}
370
+ @keyup=${() => {
371
+ const newOpenFacets = { ...this.openFacets };
372
+ newOpenFacets[key] = !isOpen;
373
+ this.openFacets = newOpenFacets;
374
+ }}
375
+ >
376
+ ${this.collapsableFacets ? collapser : nothing} ${facetGroup.title}
377
+ </h1>
378
+ ${this.moreFacetsSortingIcon(facetGroup)}
379
+ </div>
380
+ <div class="facet-group-content ${isOpen ? 'open' : ''}">
381
+ ${this.getFacetTemplate(facetGroup)}
382
+ ${this.searchMoreFacetsLink(facetGroup)}
383
+ </div>
384
+ </div>
385
+ `;
386
+ }
387
+
388
+ private moreFacetsSortingIcon(
389
+ facetGroup: FacetGroup
390
+ ): TemplateResult | typeof nothing {
391
+ // Display the sorting icon for every facet group except lending
392
+ return facetGroup.key === 'lending'
393
+ ? nothing
394
+ : html`
395
+ <input
396
+ class="sorting-icon"
397
+ type="image"
398
+ @click=${() => this.showMoreFacetsModal(facetGroup, 'alpha')}
399
+ src="https://archive.org/images/filter-count.png"
400
+ alt="Sort alphabetically"
401
+ />
402
+ `;
403
+ }
404
+
405
+ /**
406
+ * Generate the More... link button just below the facets group
407
+ *
408
+ * TODO: want to fire analytics?
409
+ */
410
+ private searchMoreFacetsLink(
411
+ facetGroup: FacetGroup
412
+ ): TemplateResult | typeof nothing {
413
+ // Don't render More... links for FTS searches
414
+ if (!this.moreLinksVisible) {
415
+ return nothing;
416
+ }
417
+
418
+ // Don't render More... links for lending facets
419
+ if (facetGroup.key === 'lending') {
420
+ return nothing;
421
+ }
422
+
423
+ // Don't render More... link if the number of facets < this.allowedFacetCount
424
+ if (Object.keys(facetGroup.buckets).length < this.allowedFacetCount) {
425
+ return nothing;
426
+ }
427
+
428
+ return html`<button
429
+ class="more-link"
430
+ @click=${() => {
431
+ this.showMoreFacetsModal(facetGroup, 'count');
432
+ this.dispatchEvent(
433
+ new CustomEvent('showMoreFacets', { detail: facetGroup.key })
434
+ );
435
+ }}
436
+ >
437
+ More...
438
+ </button>`;
439
+ }
440
+
441
+ async showMoreFacetsModal(
442
+ facetGroup: FacetGroup,
443
+ sortedBy: string
444
+ ): Promise<void> {
445
+ const facetAggrKey = facetGroup.key;
446
+
447
+ const customModalContent = html`
448
+ <more-facets-content
449
+ .facetKey=${facetGroup.key}
450
+ .facetAggregationKey=${facetAggrKey}
451
+ .fullQuery=${this.fullQuery}
452
+ .modalManager=${this.modalManager}
453
+ .searchService=${this.searchService}
454
+ .searchType=${this.searchType}
455
+ .collectionNameCache=${this.collectionNameCache}
456
+ .languageCodeHandler=${this.languageCodeHandler}
457
+ .selectedFacets=${this.selectedFacets}
458
+ .sortedBy=${sortedBy}
459
+ @facetsChanged=${(e: CustomEvent) => {
460
+ const event = new CustomEvent<SelectedFacets>('facetsChanged', {
461
+ detail: e.detail,
462
+ bubbles: true,
463
+ composed: true,
464
+ });
465
+ this.dispatchEvent(event);
466
+ }}
467
+ >
468
+ </more-facets-content>
469
+ `;
470
+
471
+ const config = new ModalConfig({
472
+ bodyColor: '#fff',
473
+ headerColor: '#194880',
474
+ showHeaderLogo: false,
475
+ closeOnBackdropClick: true, // TODO: want to fire analytics
476
+ title: html`Select filters`,
477
+ });
478
+ this.modalManager?.classList.add('more-search-facets');
479
+ this.modalManager?.showModal({
480
+ config,
481
+ customModalContent,
482
+ });
483
+ }
484
+
485
+ /**
486
+ * Generate the list template for each bucket in a facet group
487
+ */
488
+ private getFacetTemplate(facetGroup: FacetGroup): TemplateResult {
489
+ return html`
490
+ <facets-template
491
+ .facetGroup=${facetGroup}
492
+ .selectedFacets=${this.selectedFacets}
493
+ .renderOn=${'page'}
494
+ .collectionNameCache=${this.collectionNameCache}
495
+ @selectedFacetsChanged=${(e: CustomEvent) => {
496
+ const event = new CustomEvent<SelectedFacets>('facetsChanged', {
497
+ detail: e.detail,
498
+ bubbles: true,
499
+ composed: true,
500
+ });
501
+ this.dispatchEvent(event);
502
+ }}
503
+ ></facets-template>
504
+ `;
505
+ }
506
+
507
+ static get styles() {
508
+ return css`
509
+ #container.loading {
510
+ opacity: 0.5;
511
+ }
512
+
513
+ .collapser {
514
+ display: inline-block;
515
+ cursor: pointer;
516
+ width: 10px;
517
+ height: 10px;
518
+ }
519
+
520
+ .collapser svg {
521
+ transition: transform 0.2s ease-in-out;
522
+ }
523
+
524
+ .collapser.open svg {
525
+ transform: rotate(90deg);
526
+ }
527
+
528
+ .facet-group {
529
+ margin-bottom: 2rem;
530
+ }
531
+
532
+ .facet-group h1 {
533
+ margin-bottom: 0.7rem;
534
+ }
535
+
536
+ .facet-group.mobile h1 {
537
+ cursor: pointer;
538
+ }
539
+
540
+ .facet-group-header {
541
+ display: flex;
542
+ margin-bottom: 0.7rem;
543
+ justify-content: space-between;
544
+ border-bottom: 1px solid rgb(232, 232, 232);
545
+ }
546
+
547
+ .facet-group-content {
548
+ transition: max-height 0.2s ease-in-out;
549
+ }
550
+
551
+ .facet-group.mobile .facet-group-content {
552
+ max-height: 0;
553
+ overflow: hidden;
554
+ }
555
+
556
+ .facet-group.mobile .facet-group-content.open {
557
+ max-height: 2000px;
558
+ }
559
+
560
+ h1 {
561
+ font-size: 1.4rem;
562
+ font-weight: 200
563
+ padding-bottom: 3px;
564
+ margin: 0;
565
+ }
566
+
567
+ .more-link {
568
+ font-size: 1.2rem;
569
+ text-decoration: none;
570
+ padding: 0;
571
+ background: inherit;
572
+ border: 0;
573
+ color: blue;
574
+ cursor: pointer;
575
+ }
576
+ .sorting-icon {
577
+ height: 15px;
578
+ cursor: pointer;
579
+ }
580
+ `;
581
+ }
582
+ }