@internetarchive/collection-browser 2.7.2-alpha.1 → 2.7.2-alpha.3

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 (57) hide show
  1. package/.github/workflows/ci.yml +3 -2
  2. package/dist/src/app-root.d.ts +4 -0
  3. package/dist/src/app-root.js +15 -0
  4. package/dist/src/app-root.js.map +1 -1
  5. package/dist/src/assets/img/icons/filter.d.ts +2 -0
  6. package/dist/src/assets/img/icons/filter.js +10 -0
  7. package/dist/src/assets/img/icons/filter.js.map +1 -0
  8. package/dist/src/collection-browser.d.ts +3 -6
  9. package/dist/src/collection-browser.js +25 -22
  10. package/dist/src/collection-browser.js.map +1 -1
  11. package/dist/src/collection-facets/smart-facets/dedupe.d.ts +10 -0
  12. package/dist/src/collection-facets/smart-facets/dedupe.js +34 -0
  13. package/dist/src/collection-facets/smart-facets/dedupe.js.map +1 -0
  14. package/dist/src/collection-facets/smart-facets/heuristics/browser-language-heuristic.d.ts +5 -0
  15. package/dist/src/collection-facets/smart-facets/heuristics/browser-language-heuristic.js +24 -0
  16. package/dist/src/collection-facets/smart-facets/heuristics/browser-language-heuristic.js.map +1 -0
  17. package/dist/src/collection-facets/smart-facets/heuristics/query-keywords-heuristic.d.ts +5 -0
  18. package/dist/src/collection-facets/smart-facets/heuristics/query-keywords-heuristic.js +45 -0
  19. package/dist/src/collection-facets/smart-facets/heuristics/query-keywords-heuristic.js.map +1 -0
  20. package/dist/src/collection-facets/smart-facets/heuristics/wikidata-heuristic.d.ts +5 -0
  21. package/dist/src/collection-facets/smart-facets/heuristics/wikidata-heuristic.js +173 -0
  22. package/dist/src/collection-facets/smart-facets/heuristics/wikidata-heuristic.js.map +1 -0
  23. package/dist/src/collection-facets/smart-facets/models.d.ts +26 -0
  24. package/dist/src/collection-facets/smart-facets/models.js +2 -0
  25. package/dist/src/collection-facets/smart-facets/models.js.map +1 -0
  26. package/dist/src/collection-facets/smart-facets/smart-facet-bar.d.ts +30 -0
  27. package/dist/src/collection-facets/smart-facets/smart-facet-bar.js +282 -0
  28. package/dist/src/collection-facets/smart-facets/smart-facet-bar.js.map +1 -0
  29. package/dist/src/collection-facets/smart-facets/smart-facet-button.d.ts +11 -0
  30. package/dist/src/collection-facets/smart-facets/smart-facet-button.js +117 -0
  31. package/dist/src/collection-facets/smart-facets/smart-facet-button.js.map +1 -0
  32. package/dist/src/collection-facets/smart-facets/smart-facet-dropdown.d.ts +14 -0
  33. package/dist/src/collection-facets/smart-facets/smart-facet-dropdown.js +130 -0
  34. package/dist/src/collection-facets/smart-facets/smart-facet-dropdown.js.map +1 -0
  35. package/dist/src/collection-facets/smart-facets/smart-facet-equals.d.ts +2 -0
  36. package/dist/src/collection-facets/smart-facets/smart-facet-equals.js +13 -0
  37. package/dist/src/collection-facets/smart-facets/smart-facet-equals.js.map +1 -0
  38. package/dist/src/collection-facets/smart-facets/smart-facet-heuristics.d.ts +5 -0
  39. package/dist/src/collection-facets/smart-facets/smart-facet-heuristics.js +16 -0
  40. package/dist/src/collection-facets/smart-facets/smart-facet-heuristics.js.map +1 -0
  41. package/dist/src/data-source/collection-browser-data-source.js +6 -7
  42. package/dist/src/data-source/collection-browser-data-source.js.map +1 -1
  43. package/package.json +1 -1
  44. package/src/app-root.ts +16 -0
  45. package/src/assets/img/icons/filter.ts +10 -0
  46. package/src/collection-browser.ts +21 -43
  47. package/src/collection-facets/smart-facets/dedupe.ts +42 -0
  48. package/src/collection-facets/smart-facets/heuristics/browser-language-heuristic.ts +27 -0
  49. package/src/collection-facets/smart-facets/heuristics/query-keywords-heuristic.ts +55 -0
  50. package/src/collection-facets/smart-facets/heuristics/wikidata-heuristic.ts +191 -0
  51. package/src/collection-facets/smart-facets/models.ts +32 -0
  52. package/src/collection-facets/smart-facets/smart-facet-bar.ts +330 -0
  53. package/src/collection-facets/smart-facets/smart-facet-button.ts +123 -0
  54. package/src/collection-facets/smart-facets/smart-facet-dropdown.ts +137 -0
  55. package/src/collection-facets/smart-facets/smart-facet-equals.ts +16 -0
  56. package/src/collection-facets/smart-facets/smart-facet-heuristics.ts +21 -0
  57. package/src/data-source/collection-browser-data-source.ts +8 -14
@@ -0,0 +1,27 @@
1
+ import type { SmartQueryHeuristic, SmartFacet } from '../models';
2
+
3
+ export class BrowserLanguageHeuristic implements SmartQueryHeuristic {
4
+ async getRecommendedFacets(): Promise<SmartFacet[]> {
5
+ const browserLanguageCode = navigator.language;
6
+ const languageName =
7
+ BrowserLanguageHeuristic.getLanguageDisplayName(browserLanguageCode);
8
+ if (!languageName) return [];
9
+
10
+ return [
11
+ {
12
+ facets: [
13
+ {
14
+ facetType: 'language',
15
+ bucketKey: languageName,
16
+ },
17
+ ],
18
+ },
19
+ ];
20
+ }
21
+
22
+ private static getLanguageDisplayName(langCode: string): string | undefined {
23
+ // Strip off any script/region/variant codes for greater generality
24
+ const languageOnly = langCode.split('-')[0];
25
+ return new Intl.DisplayNames(['en'], { type: 'language' }).of(languageOnly);
26
+ }
27
+ }
@@ -0,0 +1,55 @@
1
+ import type {
2
+ SmartQueryHeuristic,
3
+ KeywordFacetMap,
4
+ SmartFacet,
5
+ } from '../models';
6
+
7
+ // If the query contains X word but Y facet isn't selected, recommend facet Y
8
+ export class QueryKeywordsHeuristic implements SmartQueryHeuristic {
9
+ private static readonly KEYWORDS: KeywordFacetMap = {
10
+ text: [{ facets: [{ facetType: 'mediatype', bucketKey: 'texts' }] }],
11
+ book: [{ facets: [{ facetType: 'mediatype', bucketKey: 'texts' }] }],
12
+ pdf: [{ facets: [{ facetType: 'mediatype', bucketKey: 'texts' }] }],
13
+ epub: [{ facets: [{ facetType: 'mediatype', bucketKey: 'texts' }] }],
14
+ audio: [{ facets: [{ facetType: 'mediatype', bucketKey: 'audio' }] }],
15
+ song: [{ facets: [{ facetType: 'mediatype', bucketKey: 'audio' }] }],
16
+ music: [{ facets: [{ facetType: 'mediatype', bucketKey: 'audio' }] }],
17
+ listen: [{ facets: [{ facetType: 'mediatype', bucketKey: 'audio' }] }],
18
+ podcast: [{ facets: [{ facetType: 'mediatype', bucketKey: 'audio' }] }],
19
+ radio: [{ facets: [{ facetType: 'mediatype', bucketKey: 'audio' }] }],
20
+ stream: [
21
+ { facets: [{ facetType: 'mediatype', bucketKey: 'audio' }] },
22
+ { facets: [{ facetType: 'mediatype', bucketKey: 'movies' }] },
23
+ ],
24
+ video: [{ facets: [{ facetType: 'mediatype', bucketKey: 'movies' }] }],
25
+ movie: [{ facets: [{ facetType: 'mediatype', bucketKey: 'movies' }] }],
26
+ film: [{ facets: [{ facetType: 'mediatype', bucketKey: 'movies' }] }],
27
+ image: [{ facets: [{ facetType: 'mediatype', bucketKey: 'image' }] }],
28
+ photo: [{ facets: [{ facetType: 'mediatype', bucketKey: 'image' }] }],
29
+ picture: [{ facets: [{ facetType: 'mediatype', bucketKey: 'image' }] }],
30
+ software: [{ facets: [{ facetType: 'mediatype', bucketKey: 'software' }] }],
31
+ app: [{ facets: [{ facetType: 'mediatype', bucketKey: 'software' }] }],
32
+ program: [{ facets: [{ facetType: 'mediatype', bucketKey: 'software' }] }],
33
+ game: [{ facets: [{ facetType: 'mediatype', bucketKey: 'software' }] }],
34
+ etree: [{ facets: [{ facetType: 'mediatype', bucketKey: 'etree' }] }],
35
+ concert: [{ facets: [{ facetType: 'mediatype', bucketKey: 'etree' }] }],
36
+ 'live music': [
37
+ { facets: [{ facetType: 'mediatype', bucketKey: 'etree' }] },
38
+ ],
39
+ dataset: [{ facets: [{ facetType: 'mediatype', bucketKey: 'data' }] }],
40
+ };
41
+
42
+ async getRecommendedFacets(query: string): Promise<SmartFacet[]> {
43
+ const recommendations: SmartFacet[] = [];
44
+
45
+ for (const [keyword, facets] of Object.entries(
46
+ QueryKeywordsHeuristic.KEYWORDS
47
+ )) {
48
+ if (query.includes(keyword)) {
49
+ recommendations.push(...facets);
50
+ }
51
+ }
52
+
53
+ return recommendations;
54
+ }
55
+ }
@@ -0,0 +1,191 @@
1
+ import type {
2
+ SmartQueryHeuristic,
3
+ KeywordFacetMap,
4
+ SmartFacet,
5
+ } from '../models';
6
+
7
+ // If wikidata describes the top query result as X, recommend facet Y, e.g.:
8
+ // X Y
9
+ // written work mt:texts
10
+ // film mt:movies
11
+ // author mt:texts and creator:<query>
12
+ // filmmaker mt:movies and creator:<query>
13
+ // photographer mt:image and creator:<query>
14
+ // visual artist mt:image and creator:<query>
15
+ // etc.
16
+ export class WikidataHeuristic implements SmartQueryHeuristic {
17
+ private static readonly ENTITIES: KeywordFacetMap = {
18
+ 'written work': [
19
+ { facets: [{ facetType: 'mediatype', bucketKey: 'texts' }] },
20
+ ],
21
+ literature: [{ facets: [{ facetType: 'mediatype', bucketKey: 'texts' }] }],
22
+ book: [{ facets: [{ facetType: 'mediatype', bucketKey: 'texts' }] }],
23
+ novel: [{ facets: [{ facetType: 'mediatype', bucketKey: 'texts' }] }],
24
+ filmmaker: [
25
+ {
26
+ label: 'Films by __QUERY',
27
+ facets: [
28
+ { facetType: 'mediatype', bucketKey: 'movies' },
29
+ { facetType: 'creator', bucketKey: '__QUERY' },
30
+ ],
31
+ },
32
+ ],
33
+ author: [
34
+ {
35
+ label: 'Writing by __QUERY',
36
+ facets: [
37
+ { facetType: 'mediatype', bucketKey: 'texts' },
38
+ { facetType: 'creator', bucketKey: '__QUERY' },
39
+ ],
40
+ },
41
+ ],
42
+ writer: [
43
+ {
44
+ label: 'Writing by __QUERY',
45
+ facets: [
46
+ { facetType: 'mediatype', bucketKey: 'texts' },
47
+ { facetType: 'creator', bucketKey: '__QUERY' },
48
+ ],
49
+ },
50
+ ],
51
+ poet: [
52
+ {
53
+ label: 'Writing by __QUERY',
54
+ facets: [
55
+ { facetType: 'mediatype', bucketKey: 'texts' },
56
+ { facetType: 'creator', bucketKey: '__QUERY' },
57
+ ],
58
+ },
59
+ ],
60
+ photographer: [
61
+ {
62
+ label: 'Images by __QUERY',
63
+ facets: [
64
+ { facetType: 'mediatype', bucketKey: 'image' },
65
+ { facetType: 'creator', bucketKey: '__QUERY' },
66
+ ],
67
+ },
68
+ ],
69
+ painter: [
70
+ {
71
+ label: 'Images by __QUERY',
72
+ facets: [
73
+ { facetType: 'mediatype', bucketKey: 'image' },
74
+ { facetType: 'creator', bucketKey: '__QUERY' },
75
+ ],
76
+ },
77
+ ],
78
+ 'visual artist': [
79
+ {
80
+ label: 'Images by __QUERY',
81
+ facets: [
82
+ { facetType: 'mediatype', bucketKey: 'image' },
83
+ { facetType: 'creator', bucketKey: '__QUERY' },
84
+ ],
85
+ },
86
+ ],
87
+ 'graphic artist': [
88
+ {
89
+ label: 'Images by __QUERY',
90
+ facets: [
91
+ { facetType: 'mediatype', bucketKey: 'image' },
92
+ { facetType: 'creator', bucketKey: '__QUERY' },
93
+ ],
94
+ },
95
+ ],
96
+ singer: [
97
+ {
98
+ label: 'Music by __QUERY',
99
+ facets: [
100
+ { facetType: 'mediatype', bucketKey: 'audio' },
101
+ { facetType: 'creator', bucketKey: '__QUERY' },
102
+ ],
103
+ },
104
+ ],
105
+ songwriter: [
106
+ {
107
+ label: 'Music by __QUERY',
108
+ facets: [
109
+ { facetType: 'mediatype', bucketKey: 'audio' },
110
+ { facetType: 'creator', bucketKey: '__QUERY' },
111
+ ],
112
+ },
113
+ ],
114
+ musician: [
115
+ {
116
+ label: 'Music by __QUERY',
117
+ facets: [
118
+ { facetType: 'mediatype', bucketKey: 'audio' },
119
+ { facetType: 'creator', bucketKey: '__QUERY' },
120
+ ],
121
+ },
122
+ ],
123
+ composer: [
124
+ {
125
+ label: 'Music by __QUERY',
126
+ facets: [
127
+ { facetType: 'mediatype', bucketKey: 'audio' },
128
+ { facetType: 'creator', bucketKey: '__QUERY' },
129
+ ],
130
+ },
131
+ ],
132
+ pianist: [
133
+ {
134
+ label: 'Music by __QUERY',
135
+ facets: [
136
+ { facetType: 'mediatype', bucketKey: 'audio' },
137
+ { facetType: 'creator', bucketKey: '__QUERY' },
138
+ ],
139
+ },
140
+ ],
141
+ };
142
+
143
+ async getRecommendedFacets(query: string): Promise<SmartFacet[]> {
144
+ const recommendations: SmartFacet[] = [];
145
+
146
+ try {
147
+ const urlQuery = encodeURIComponent(query);
148
+
149
+ const wikidataResponse = await fetch(
150
+ `https://www.wikidata.org/w/api.php?action=wbsearchentities&search=${urlQuery}&format=json&language=en&uselang=en&origin=*&type=item&limit=5`
151
+ );
152
+ const searchResults = await wikidataResponse.json();
153
+
154
+ for (const [keyword, facets] of Object.entries(
155
+ WikidataHeuristic.ENTITIES
156
+ )) {
157
+ const keywordRegex = new RegExp(`\\b${keyword}\\b`);
158
+ if (keywordRegex.test(searchResults.search[0]?.description)) {
159
+ const entityName = searchResults.search[0].label;
160
+ recommendations.push(
161
+ ...facets.map(
162
+ sf =>
163
+ ({
164
+ label: sf.label?.replace('__QUERY', entityName),
165
+ facets: sf.facets.map(f => {
166
+ const replaced = {
167
+ ...f,
168
+ bucketKey: f.bucketKey.replace('__QUERY', query),
169
+ };
170
+
171
+ if (f.displayText) {
172
+ replaced.displayText = replaced.displayText?.replace(
173
+ '__QUERY',
174
+ entityName
175
+ );
176
+ }
177
+
178
+ return replaced;
179
+ }),
180
+ } as SmartFacet)
181
+ )
182
+ );
183
+ }
184
+ }
185
+
186
+ return recommendations;
187
+ } catch (err) {
188
+ return [];
189
+ }
190
+ }
191
+ }
@@ -0,0 +1,32 @@
1
+ import type { FacetEventDetails, FacetOption } from '../../models';
2
+
3
+ export interface FacetRef {
4
+ facetType: FacetOption;
5
+ bucketKey: string;
6
+ displayText?: string;
7
+ }
8
+
9
+ interface LabeledSmartFacet {
10
+ label: string;
11
+ facets: FacetRef[];
12
+ selected?: boolean;
13
+ }
14
+
15
+ interface UnlabeledSmartFacet {
16
+ label?: string;
17
+ facets: [FacetRef];
18
+ selected?: boolean;
19
+ }
20
+
21
+ export type SmartFacet = LabeledSmartFacet | UnlabeledSmartFacet;
22
+
23
+ export interface SmartFacetEvent {
24
+ smartFacet: SmartFacet;
25
+ details: FacetEventDetails[];
26
+ }
27
+
28
+ export type KeywordFacetMap = Record<string, SmartFacet[]>;
29
+
30
+ export interface SmartQueryHeuristic {
31
+ getRecommendedFacets(query: string): Promise<SmartFacet[]>;
32
+ }
@@ -0,0 +1,330 @@
1
+ /* eslint-disable no-continue */
2
+
3
+ import {
4
+ css,
5
+ html,
6
+ LitElement,
7
+ TemplateResult,
8
+ CSSResultGroup,
9
+ nothing,
10
+ PropertyValues,
11
+ } from 'lit';
12
+ import { repeat } from 'lit/directives/repeat.js';
13
+ import { customElement, property, state } from 'lit/decorators.js';
14
+ import type { Aggregation, Bucket } from '@internetarchive/search-service';
15
+ import type { CollectionTitles } from '../../data-source/models';
16
+ import type { FacetOption, SelectedFacets } from '../../models';
17
+ import { updateSelectedFacetBucket } from '../../utils/facet-utils';
18
+ import { SmartQueryHeuristicGroup } from './smart-facet-heuristics';
19
+ import type { SmartFacet, SmartFacetEvent } from './models';
20
+ import filterIcon from '../../assets/img/icons/filter';
21
+
22
+ import './smart-facet-button';
23
+ import './smart-facet-dropdown';
24
+ import { smartFacetEquals } from './smart-facet-equals';
25
+ import { dedupe } from './dedupe';
26
+
27
+ const fieldPrefixes: Partial<Record<FacetOption, string>> = {
28
+ collection: 'Collection: ',
29
+ creator: 'By: ',
30
+ subject: 'About: ',
31
+ };
32
+
33
+ function capitalize(str: string) {
34
+ return str.charAt(0).toUpperCase() + str.slice(1);
35
+ }
36
+
37
+ @customElement('smart-facet-bar')
38
+ export class SmartFacetBar extends LitElement {
39
+ @property({ type: String }) query?: string;
40
+
41
+ @property({ type: Object }) aggregations?: Record<string, Aggregation>;
42
+
43
+ @property({ type: Object }) selectedFacets?: SelectedFacets;
44
+
45
+ /** The map from collection identifiers to their titles */
46
+ @property({ type: Object })
47
+ collectionTitles?: CollectionTitles;
48
+
49
+ @property({ type: Boolean }) filterToggleActive = false;
50
+
51
+ @state() private heuristicRecs: SmartFacet[] = [];
52
+
53
+ @state() private smartFacets: SmartFacet[][] = [];
54
+
55
+ @state() private lastAggregations?: Record<string, Aggregation>;
56
+
57
+ //
58
+ // COMPONENT LIFECYCLE METHODS
59
+ //
60
+
61
+ render() {
62
+ return html`
63
+ <div id="smart-facets-container">
64
+ ${this.filtersToggleTemplate}
65
+ ${repeat(
66
+ this.smartFacets,
67
+ f =>
68
+ `${f[0].label}|${f[0].facets[0].facetType}|${f[0].facets[0].bucketKey}`,
69
+ facet => this.makeSmartFacet(facet)
70
+ )}
71
+ </div>
72
+ `;
73
+ }
74
+
75
+ protected willUpdate(changed: PropertyValues): void {
76
+ if (changed.has('query')) {
77
+ this.updateSmartFacets();
78
+ this.lastAggregations = undefined;
79
+ }
80
+
81
+ if (
82
+ changed.has('aggregations') &&
83
+ !this.lastAggregations &&
84
+ this.aggregations &&
85
+ Object.keys(this.aggregations).length > 0
86
+ ) {
87
+ this.lastAggregations = this.aggregations;
88
+ }
89
+ }
90
+
91
+ private async updateSmartFacets(): Promise<void> {
92
+ console.log('updating smart facets');
93
+ if (this.query) {
94
+ this.heuristicRecs =
95
+ await new SmartQueryHeuristicGroup().getRecommendedFacets(this.query);
96
+ this.smartFacets = dedupe(this.facetsToDisplay);
97
+ }
98
+ }
99
+
100
+ //
101
+ // OTHER METHODS
102
+ //
103
+
104
+ private makeSmartFacet(facets: SmartFacet[]) {
105
+ if (facets.length === 0) {
106
+ return nothing;
107
+ }
108
+ if (facets.length === 1) {
109
+ return this.smartFacetButton(facets[0]);
110
+ }
111
+ return this.smartFacetDropdown(facets);
112
+ }
113
+
114
+ private smartFacetButton(facet: SmartFacet) {
115
+ return html`
116
+ <smart-facet-button
117
+ .facetInfo=${facet}
118
+ .labelPrefix=${fieldPrefixes[facet.facets[0].facetType]}
119
+ .selected=${facet.selected ?? false}
120
+ @facetClick=${this.facetClicked}
121
+ ></smart-facet-button>
122
+ `;
123
+ }
124
+
125
+ private smartFacetDropdown(facets: SmartFacet[]) {
126
+ return html`
127
+ <smart-facet-dropdown
128
+ .facetInfo=${facets}
129
+ .labelPrefix=${fieldPrefixes[facets[0].facets[0].facetType]}
130
+ .activeFacetRef=${facets[0].facets[0]}
131
+ @facetClick=${this.facetDropdownClicked}
132
+ ></smart-facet-dropdown>
133
+ `;
134
+ }
135
+
136
+ private get filtersToggleTemplate(): TemplateResult {
137
+ return html`
138
+ <button
139
+ id="filters-toggle"
140
+ class=${this.filterToggleActive ? 'active' : ''}
141
+ title="${this.filterToggleActive ? 'Hide' : 'Show'} filters pane"
142
+ @click=${this.filterToggleClicked}
143
+ >
144
+ ${filterIcon}
145
+ </button>
146
+ `;
147
+ }
148
+
149
+ private get facetsToDisplay(): SmartFacet[][] {
150
+ if (!this.lastAggregations) return [];
151
+
152
+ const facets: SmartFacet[][] = [];
153
+
154
+ if (this.heuristicRecs.length > 0) {
155
+ for (const rec of this.heuristicRecs) {
156
+ facets.push([rec]);
157
+ }
158
+ }
159
+
160
+ const keys = [
161
+ 'mediatype',
162
+ 'year',
163
+ 'language',
164
+ 'creator',
165
+ 'subject',
166
+ 'collection',
167
+ ];
168
+ for (const key of keys) {
169
+ const agg = this.lastAggregations[key];
170
+ if (!agg) continue;
171
+ if (agg.buckets.length === 0) continue;
172
+ if (['lending', 'year_histogram'].includes(key)) continue;
173
+ if (typeof agg.buckets[0] === 'number') continue;
174
+
175
+ if (
176
+ key === 'mediatype' &&
177
+ this.selectedFacets &&
178
+ Object.values(this.selectedFacets.mediatype).some(
179
+ bucket => bucket.state !== 'none'
180
+ )
181
+ ) {
182
+ continue;
183
+ }
184
+
185
+ const facetType = key as FacetOption;
186
+ const buckets = agg.buckets as Bucket[];
187
+
188
+ const unusedBuckets = buckets.filter(b => {
189
+ const selectedFacetBucket = this.selectedFacets?.[facetType][b.key];
190
+ if (selectedFacetBucket && selectedFacetBucket.state !== 'none') {
191
+ return false;
192
+ }
193
+ return true;
194
+ });
195
+
196
+ if (facetType === 'mediatype') {
197
+ facets.push(
198
+ [this.toSmartFacet(facetType, [unusedBuckets[0]])],
199
+ [this.toSmartFacet(facetType, [unusedBuckets[1]])]
200
+ );
201
+ } else if (facetType === 'collection' || facetType === 'subject') {
202
+ const topBuckets = unusedBuckets.slice(0, 5);
203
+ facets.push(topBuckets.map(b => this.toSmartFacet(facetType, [b])));
204
+ } else {
205
+ facets.push([this.toSmartFacet(facetType, [unusedBuckets[0]])]);
206
+ }
207
+ }
208
+
209
+ return facets;
210
+ }
211
+
212
+ private toSmartFacet(
213
+ facetType: FacetOption,
214
+ buckets: Bucket[]
215
+ // prefix = true
216
+ ): SmartFacet {
217
+ return {
218
+ facets: buckets.map(bucket => {
219
+ let displayText = capitalize(bucket.key.toString());
220
+ if (facetType === 'collection') {
221
+ const title = this.collectionTitles?.get(bucket.key.toString());
222
+ if (title) displayText = title;
223
+ }
224
+
225
+ // if (prefix && fieldPrefixes[facetType]) {
226
+ // displayText = fieldPrefixes[facetType] + displayText;
227
+ // }
228
+
229
+ return {
230
+ facetType,
231
+ bucketKey: bucket.key.toString(),
232
+ displayText,
233
+ };
234
+ }),
235
+ } as SmartFacet;
236
+ }
237
+
238
+ private facetClicked(e: CustomEvent<SmartFacetEvent>): void {
239
+ this.smartFacets = [
240
+ [{ ...e.detail.smartFacet, selected: !e.detail.smartFacet.selected }],
241
+ ...this.smartFacets.filter(f => f[0] !== e.detail.smartFacet),
242
+ ];
243
+
244
+ for (const facet of e.detail.details) {
245
+ this.selectedFacets = updateSelectedFacetBucket(
246
+ this.selectedFacets,
247
+ facet.facetType,
248
+ facet.bucket,
249
+ true
250
+ );
251
+ }
252
+
253
+ const event = new CustomEvent<SelectedFacets>('facetsChanged', {
254
+ detail: this.selectedFacets,
255
+ });
256
+ this.dispatchEvent(event);
257
+ }
258
+
259
+ private facetDropdownClicked(e: CustomEvent<SmartFacetEvent>): void {
260
+ if (
261
+ this.smartFacets.find(sf => smartFacetEquals(sf[0], e.detail.smartFacet))
262
+ ) {
263
+ return;
264
+ }
265
+
266
+ this.smartFacets = [
267
+ [{ ...e.detail.smartFacet, selected: true }],
268
+ ...this.smartFacets,
269
+ ];
270
+
271
+ for (const facet of e.detail.details) {
272
+ this.selectedFacets = updateSelectedFacetBucket(
273
+ this.selectedFacets,
274
+ facet.facetType,
275
+ facet.bucket,
276
+ true
277
+ );
278
+ }
279
+
280
+ const event = new CustomEvent<SelectedFacets>('facetsChanged', {
281
+ detail: this.selectedFacets,
282
+ });
283
+ this.dispatchEvent(event);
284
+ }
285
+
286
+ private filterToggleClicked(): void {
287
+ this.dispatchEvent(new CustomEvent('filtersToggled'));
288
+ }
289
+
290
+ //
291
+ // STYLES
292
+ //
293
+
294
+ static get styles(): CSSResultGroup {
295
+ return css`
296
+ #smart-facets-container {
297
+ display: flex;
298
+ align-items: center;
299
+ flex-wrap: wrap;
300
+ gap: 5px;
301
+ padding: 10px 0;
302
+ }
303
+
304
+ #filters-toggle {
305
+ margin: 0;
306
+ border: 0;
307
+ padding: 5px 10px;
308
+ border-radius: 15px;
309
+ background: #194880;
310
+ color: white;
311
+ font-size: 1.6rem;
312
+ font-family: inherit;
313
+ text-decoration: none;
314
+ box-shadow: 1px 1px rgba(0, 0, 0, 0.4);
315
+ cursor: pointer;
316
+ }
317
+
318
+ #filters-toggle.active {
319
+ background: #09294d;
320
+ box-shadow: -1px -1px rgba(0, 0, 0, 0.1);
321
+ }
322
+
323
+ #filters-toggle > svg {
324
+ width: 15px;
325
+ filter: invert(1);
326
+ vertical-align: text-bottom;
327
+ }
328
+ `;
329
+ }
330
+ }