@internetarchive/collection-browser 0.0.1-alpha.2 → 0.0.1-alpha.22

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 (183) hide show
  1. package/demo/app-root.ts +24 -158
  2. package/dist/demo/app-root.d.ts +2 -16
  3. package/dist/demo/app-root.js +23 -141
  4. package/dist/demo/app-root.js.map +1 -1
  5. package/dist/src/assets/img/icons/chevron.d.ts +2 -0
  6. package/dist/src/assets/img/icons/chevron.js +4 -0
  7. package/dist/src/assets/img/icons/chevron.js.map +1 -0
  8. package/dist/src/assets/img/icons/eye-closed.d.ts +2 -0
  9. package/dist/src/assets/img/icons/eye-closed.js +5 -0
  10. package/dist/src/assets/img/icons/eye-closed.js.map +1 -0
  11. package/dist/src/assets/img/icons/eye.d.ts +2 -0
  12. package/dist/src/assets/img/icons/eye.js +5 -0
  13. package/dist/src/assets/img/icons/eye.js.map +1 -0
  14. package/dist/src/assets/img/icons/mediatype/account.d.ts +1 -2
  15. package/dist/src/assets/img/icons/mediatype/account.js +5 -4
  16. package/dist/src/assets/img/icons/mediatype/account.js.map +1 -1
  17. package/dist/src/assets/img/icons/mediatype/audio.js +7 -4
  18. package/dist/src/assets/img/icons/mediatype/audio.js.map +1 -1
  19. package/dist/src/assets/img/icons/mediatype/collection.js +7 -4
  20. package/dist/src/assets/img/icons/mediatype/collection.js.map +1 -1
  21. package/dist/src/assets/img/icons/mediatype/etree.js +10 -5
  22. package/dist/src/assets/img/icons/mediatype/etree.js.map +1 -1
  23. package/dist/src/assets/img/icons/mediatype/film.js +2 -1
  24. package/dist/src/assets/img/icons/mediatype/film.js.map +1 -1
  25. package/dist/src/assets/img/icons/mediatype/images.js +9 -6
  26. package/dist/src/assets/img/icons/mediatype/images.js.map +1 -1
  27. package/dist/src/assets/img/icons/mediatype/software.js +9 -6
  28. package/dist/src/assets/img/icons/mediatype/software.js.map +1 -1
  29. package/dist/src/assets/img/icons/mediatype/texts.js +9 -6
  30. package/dist/src/assets/img/icons/mediatype/texts.js.map +1 -1
  31. package/dist/src/assets/img/icons/mediatype/tv.js +10 -5
  32. package/dist/src/assets/img/icons/mediatype/tv.js.map +1 -1
  33. package/dist/src/assets/img/icons/mediatype/video.js +10 -6
  34. package/dist/src/assets/img/icons/mediatype/video.js.map +1 -1
  35. package/dist/src/assets/img/icons/mediatype/web.js +9 -6
  36. package/dist/src/assets/img/icons/mediatype/web.js.map +1 -1
  37. package/dist/src/async-collection-name.d.ts +11 -0
  38. package/dist/src/async-collection-name.js +38 -0
  39. package/dist/src/async-collection-name.js.map +1 -0
  40. package/dist/src/collection-browser.d.ts +55 -17
  41. package/dist/src/collection-browser.js +466 -106
  42. package/dist/src/collection-browser.js.map +1 -1
  43. package/dist/src/collection-facets.d.ts +24 -5
  44. package/dist/src/collection-facets.js +300 -78
  45. package/dist/src/collection-facets.js.map +1 -1
  46. package/dist/src/collection-name-cache.d.ts +18 -0
  47. package/dist/src/collection-name-cache.js +89 -0
  48. package/dist/src/collection-name-cache.js.map +1 -0
  49. package/dist/src/mediatype-icon.js +10 -3
  50. package/dist/src/mediatype-icon.js.map +1 -1
  51. package/dist/src/models.d.ts +72 -14
  52. package/dist/src/models.js +57 -1
  53. package/dist/src/models.js.map +1 -1
  54. package/dist/src/restoration-state-handler.d.ts +37 -0
  55. package/dist/src/restoration-state-handler.js +177 -0
  56. package/dist/src/restoration-state-handler.js.map +1 -0
  57. package/dist/src/sort-filter-bar/alpha-bar.d.ts +1 -2
  58. package/dist/src/sort-filter-bar/alpha-bar.js +19 -9
  59. package/dist/src/sort-filter-bar/alpha-bar.js.map +1 -1
  60. package/dist/src/sort-filter-bar/img/compact.d.ts +1 -0
  61. package/dist/src/sort-filter-bar/img/compact.js +5 -0
  62. package/dist/src/sort-filter-bar/img/compact.js.map +1 -0
  63. package/dist/src/sort-filter-bar/img/grid.d.ts +1 -0
  64. package/dist/src/sort-filter-bar/img/grid.js +5 -0
  65. package/dist/src/sort-filter-bar/img/grid.js.map +1 -0
  66. package/dist/src/sort-filter-bar/img/list.d.ts +1 -0
  67. package/dist/src/sort-filter-bar/img/list.js +5 -0
  68. package/dist/src/sort-filter-bar/img/list.js.map +1 -0
  69. package/dist/src/sort-filter-bar/img/sort-triangle.d.ts +1 -0
  70. package/dist/src/sort-filter-bar/img/sort-triangle.js +5 -0
  71. package/dist/src/sort-filter-bar/img/sort-triangle.js.map +1 -0
  72. package/dist/src/sort-filter-bar/img/tile.d.ts +1 -0
  73. package/dist/src/sort-filter-bar/img/tile.js +5 -0
  74. package/dist/src/sort-filter-bar/img/tile.js.map +1 -0
  75. package/dist/src/sort-filter-bar/sort-filter-bar.d.ts +65 -11
  76. package/dist/src/sort-filter-bar/sort-filter-bar.js +453 -142
  77. package/dist/src/sort-filter-bar/sort-filter-bar.js.map +1 -1
  78. package/dist/src/tiles/grid/collection-tile.js +1 -2
  79. package/dist/src/tiles/grid/collection-tile.js.map +1 -1
  80. package/dist/src/tiles/grid/item-tile.d.ts +4 -0
  81. package/dist/src/tiles/grid/item-tile.js +134 -45
  82. package/dist/src/tiles/grid/item-tile.js.map +1 -1
  83. package/dist/src/tiles/list/tile-list-compact-header.d.ts +11 -0
  84. package/dist/src/tiles/list/tile-list-compact-header.js +79 -0
  85. package/dist/src/tiles/list/tile-list-compact-header.js.map +1 -0
  86. package/dist/src/tiles/list/tile-list-compact.d.ts +1 -0
  87. package/dist/src/tiles/list/tile-list-compact.js +122 -31
  88. package/dist/src/tiles/list/tile-list-compact.js.map +1 -1
  89. package/dist/src/tiles/list/tile-list-detail.d.ts +0 -10
  90. package/dist/src/tiles/list/tile-list-detail.js +6 -159
  91. package/dist/src/tiles/list/tile-list-detail.js.map +1 -1
  92. package/dist/src/tiles/list/tile-list.d.ts +19 -6
  93. package/dist/src/tiles/list/tile-list.js +240 -108
  94. package/dist/src/tiles/list/tile-list.js.map +1 -1
  95. package/dist/src/tiles/tile-dispatcher.d.ts +8 -1
  96. package/dist/src/tiles/tile-dispatcher.js +46 -11
  97. package/dist/src/tiles/tile-dispatcher.js.map +1 -1
  98. package/dist/src/utils/format-date.js +1 -2
  99. package/dist/src/utils/format-date.js.map +1 -1
  100. package/dist/test/{utils/format-string.test.d.ts → collection-name-cache.test.d.ts} +0 -0
  101. package/dist/test/collection-name-cache.test.js +158 -0
  102. package/dist/test/collection-name-cache.test.js.map +1 -0
  103. package/dist/test/mocks/mock-search-response.d.ts +5 -0
  104. package/dist/test/mocks/mock-search-response.js +62 -0
  105. package/dist/test/mocks/mock-search-response.js.map +1 -0
  106. package/dist/test/mocks/mock-search-service.d.ts +13 -0
  107. package/dist/test/mocks/mock-search-service.js +20 -0
  108. package/dist/test/mocks/mock-search-service.js.map +1 -0
  109. package/package.json +9 -4
  110. package/src/assets/img/icons/chevron.ts +4 -0
  111. package/src/assets/img/icons/eye-closed.ts +5 -0
  112. package/src/assets/img/icons/eye.ts +5 -0
  113. package/src/assets/img/icons/mediatype/account.ts +5 -4
  114. package/src/assets/img/icons/mediatype/audio.ts +7 -4
  115. package/src/assets/img/icons/mediatype/collection.ts +7 -4
  116. package/src/assets/img/icons/mediatype/etree.ts +10 -5
  117. package/src/assets/img/icons/mediatype/film.ts +2 -1
  118. package/src/assets/img/icons/mediatype/images.ts +9 -6
  119. package/src/assets/img/icons/mediatype/software.ts +9 -6
  120. package/src/assets/img/icons/mediatype/texts.ts +9 -6
  121. package/src/assets/img/icons/mediatype/tv.ts +10 -5
  122. package/src/assets/img/icons/mediatype/video.ts +10 -6
  123. package/src/assets/img/icons/mediatype/web.ts +9 -6
  124. package/src/collection-browser.ts +490 -105
  125. package/src/collection-facets.ts +325 -109
  126. package/src/mediatype-icon.ts +10 -3
  127. package/src/models.ts +139 -14
  128. package/src/restoration-state-handler.ts +234 -0
  129. package/src/sort-filter-bar/alpha-bar.ts +19 -9
  130. package/src/sort-filter-bar/img/compact.ts +5 -0
  131. package/src/sort-filter-bar/img/list.ts +5 -0
  132. package/src/sort-filter-bar/img/sort-triangle.ts +5 -0
  133. package/src/sort-filter-bar/img/tile.ts +5 -0
  134. package/src/sort-filter-bar/sort-filter-bar.ts +499 -149
  135. package/src/tiles/grid/collection-tile.ts +1 -2
  136. package/src/tiles/grid/item-tile.ts +138 -56
  137. package/src/tiles/list/tile-list-compact-header.ts +75 -0
  138. package/src/tiles/list/tile-list-compact.ts +209 -0
  139. package/src/tiles/list/tile-list.ts +261 -110
  140. package/src/tiles/tile-dispatcher.ts +51 -11
  141. package/src/utils/format-date.ts +1 -2
  142. package/dist/src/assets/img/icons/audio.d.ts +0 -1
  143. package/dist/src/assets/img/icons/audio.js +0 -9
  144. package/dist/src/assets/img/icons/audio.js.map +0 -1
  145. package/dist/src/assets/img/icons/collection.d.ts +0 -1
  146. package/dist/src/assets/img/icons/collection.js +0 -9
  147. package/dist/src/assets/img/icons/collection.js.map +0 -1
  148. package/dist/src/assets/img/icons/etree.d.ts +0 -1
  149. package/dist/src/assets/img/icons/etree.js +0 -9
  150. package/dist/src/assets/img/icons/etree.js.map +0 -1
  151. package/dist/src/assets/img/icons/images.d.ts +0 -1
  152. package/dist/src/assets/img/icons/images.js +0 -10
  153. package/dist/src/assets/img/icons/images.js.map +0 -1
  154. package/dist/src/assets/img/icons/mediatype/etree copy.d.ts +0 -1
  155. package/dist/src/assets/img/icons/mediatype/etree copy.js +0 -9
  156. package/dist/src/assets/img/icons/mediatype/etree copy.js.map +0 -1
  157. package/dist/src/assets/img/icons/mediatype/livemusic.d.ts +0 -1
  158. package/dist/src/assets/img/icons/mediatype/livemusic.js +0 -7
  159. package/dist/src/assets/img/icons/mediatype/livemusic.js.map +0 -1
  160. package/dist/src/assets/img/icons/mediatype/photos.d.ts +0 -1
  161. package/dist/src/assets/img/icons/mediatype/photos.js +0 -7
  162. package/dist/src/assets/img/icons/mediatype/photos.js.map +0 -1
  163. package/dist/src/assets/img/icons/software.d.ts +0 -1
  164. package/dist/src/assets/img/icons/software.js +0 -10
  165. package/dist/src/assets/img/icons/software.js.map +0 -1
  166. package/dist/src/assets/img/icons/texts.d.ts +0 -1
  167. package/dist/src/assets/img/icons/texts.js +0 -10
  168. package/dist/src/assets/img/icons/texts.js.map +0 -1
  169. package/dist/src/assets/img/icons/tv.d.ts +0 -1
  170. package/dist/src/assets/img/icons/tv.js +0 -9
  171. package/dist/src/assets/img/icons/tv.js.map +0 -1
  172. package/dist/src/assets/img/icons/video.d.ts +0 -1
  173. package/dist/src/assets/img/icons/video.js +0 -10
  174. package/dist/src/assets/img/icons/video.js.map +0 -1
  175. package/dist/src/assets/img/icons/web.d.ts +0 -1
  176. package/dist/src/assets/img/icons/web.js +0 -10
  177. package/dist/src/assets/img/icons/web.js.map +0 -1
  178. package/dist/src/utils/format-string.d.ts +0 -2
  179. package/dist/src/utils/format-string.js +0 -7
  180. package/dist/src/utils/format-string.js.map +0 -1
  181. package/dist/test/utils/format-string.test.js +0 -17
  182. package/dist/test/utils/format-string.test.js.map +0 -1
  183. package/src/assets/img/icons/mediatype/foo.svg +0 -5
@@ -8,6 +8,7 @@ import {
8
8
  nothing,
9
9
  } from 'lit';
10
10
  import { customElement, property, query, state } from 'lit/decorators.js';
11
+ import { ifDefined } from 'lit/directives/if-defined.js';
11
12
  import type {
12
13
  InfiniteScroller,
13
14
  InfiniteScrollerCellProviderInterface,
@@ -17,26 +18,46 @@ import {
17
18
  Metadata,
18
19
  SearchParams,
19
20
  SearchServiceInterface,
21
+ SortDirection,
20
22
  } from '@internetarchive/search-service';
21
23
  import {
22
24
  AggregateSearchParams,
23
25
  SortParam,
24
- } from '@internetarchive/search-service/dist/src/search-params';
25
- import { SharedResizeObserverInterface } from '@internetarchive/shared-resize-observer';
26
- import type { TileModel, CollectionDisplayMode } from './models';
26
+ } from '@internetarchive/search-service';
27
+ import {
28
+ SharedResizeObserverInterface,
29
+ SharedResizeObserverResizeHandlerInterface,
30
+ } from '@internetarchive/shared-resize-observer';
27
31
  import '@internetarchive/infinite-scroller';
28
- import '@internetarchive/histogram-date-range';
32
+ import type { CollectionNameCacheInterface } from '@internetarchive/collection-name-cache';
29
33
  import './tiles/tile-dispatcher';
30
34
  import './tiles/loading-tile';
31
35
  import './sort-filter-bar/sort-filter-bar';
32
36
  import './collection-facets';
33
- import { CollectionFacets } from './collection-facets';
34
37
  import './circular-activity-indicator';
38
+ import './sort-filter-bar/sort-filter-bar';
39
+ import {
40
+ SelectedFacets,
41
+ SortField,
42
+ SortFieldToMetadataField,
43
+ CollectionBrowserContext,
44
+ defaultSelectedFacets,
45
+ TileModel,
46
+ CollectionDisplayMode,
47
+ } from './models';
48
+ import {
49
+ RestorationStateHandlerInterface,
50
+ RestorationStateHandler,
51
+ RestorationState,
52
+ } from './restoration-state-handler';
53
+ import chevronIcon from './assets/img/icons/chevron';
35
54
 
36
55
  @customElement('collection-browser')
37
56
  export class CollectionBrowser
38
57
  extends LitElement
39
- implements InfiniteScrollerCellProviderInterface
58
+ implements
59
+ InfiniteScrollerCellProviderInterface,
60
+ SharedResizeObserverResizeHandlerInterface
40
61
  {
41
62
  @property({ type: String }) baseNavigationUrl?: string;
42
63
 
@@ -46,11 +67,17 @@ export class CollectionBrowser
46
67
 
47
68
  @property({ type: Boolean }) showDeleteButtons = false;
48
69
 
49
- @property({ type: String }) displayMode: CollectionDisplayMode = 'grid';
70
+ @property({ type: String }) displayMode?: CollectionDisplayMode;
71
+
72
+ @property({ type: Object }) sortParam: SortParam | null = null;
73
+
74
+ @property({ type: String }) selectedSort: SortField = SortField.relevance;
50
75
 
51
- @property({ type: Object }) sortParam?: SortParam;
76
+ @property({ type: String }) selectedTitleFilter: string | null = null;
52
77
 
53
- @property({ type: String }) additionalQueryClause?: string;
78
+ @property({ type: String }) selectedCreatorFilter: string | null = null;
79
+
80
+ @property({ type: String }) sortDirection: SortDirection | null = null;
54
81
 
55
82
  @property({ type: String }) dateRangeQueryClause?: string;
56
83
 
@@ -58,7 +85,31 @@ export class CollectionBrowser
58
85
 
59
86
  @property({ type: Object }) resizeObserver?: SharedResizeObserverInterface;
60
87
 
61
- @query('collection-facets') collectionFacets!: CollectionFacets;
88
+ @property({ type: String }) titleQuery?: string;
89
+
90
+ @property({ type: String }) creatorQuery?: string;
91
+
92
+ @property({ type: Number }) currentPage?: number;
93
+
94
+ @property({ type: String }) minSelectedDate?: string;
95
+
96
+ @property({ type: String }) maxSelectedDate?: string;
97
+
98
+ @property({ type: Object }) selectedFacets?: SelectedFacets;
99
+
100
+ @property({ type: Object })
101
+ collectionNameCache?: CollectionNameCacheInterface;
102
+
103
+ @property({ type: String }) pageContext: CollectionBrowserContext = 'search';
104
+
105
+ @property({ type: Object })
106
+ restorationStateHandler: RestorationStateHandlerInterface = new RestorationStateHandler(
107
+ {
108
+ context: this.pageContext,
109
+ }
110
+ );
111
+
112
+ @property({ type: Number }) mobileBreakpoint = 530;
62
113
 
63
114
  /**
64
115
  * The page that the consumer wants to load.
@@ -75,8 +126,6 @@ export class CollectionBrowser
75
126
 
76
127
  @state() private searchResultsLoading = false;
77
128
 
78
- @state() private selectedFacets: Record<string, string[]> = {};
79
-
80
129
  @state() private facetsLoading = false;
81
130
 
82
131
  @state() private fullYearAggregationLoading = false;
@@ -85,6 +134,14 @@ export class CollectionBrowser
85
134
 
86
135
  @state() private fullYearsHistogramAggregation: Aggregation | undefined;
87
136
 
137
+ @state() private totalResults?: number;
138
+
139
+ @state() private mobileView = false;
140
+
141
+ @state() private mobileFacetsVisible = false;
142
+
143
+ @query('#content-container') private contentContainer!: HTMLDivElement;
144
+
88
145
  /**
89
146
  * When we're animated scrolling to the page, we don't want to fetch
90
147
  * all of the pages as it scrolls so this lets us know if we're scrolling
@@ -116,6 +173,11 @@ export class CollectionBrowser
116
173
  return model;
117
174
  }
118
175
 
176
+ private get sortFilterQueries(): string {
177
+ const queries = [this.titleQuery, this.creatorQuery];
178
+ return queries.filter(q => q).join(' AND ');
179
+ }
180
+
119
181
  // this is the total number of tiles we expect if
120
182
  // the data returned is a full page worth
121
183
  // this is useful for putting in placeholders for the expected number of tiles
@@ -146,9 +208,9 @@ export class CollectionBrowser
146
208
  private infiniteScroller!: InfiniteScroller;
147
209
 
148
210
  /**
211
+ * Go to the given page of results
149
212
  *
150
213
  * @param pageNumber
151
- * @param scroll
152
214
  */
153
215
  goToPage(pageNumber: number) {
154
216
  this.initialPageNumber = pageNumber;
@@ -158,34 +220,71 @@ export class CollectionBrowser
158
220
 
159
221
  render() {
160
222
  return html`
161
- <h1>Collection Browser</h1>
162
-
163
- <div id="query">
164
- <ul>
165
- <li>Base Query: ${this.baseQuery}</li>
166
- <li>Facet Query: ${this.facetQuery}</li>
167
- <li>Additional Query: ${this.additionalQueryClause}</li>
168
- <li>Date Range Query: ${this.dateRangeQueryClause}</li>
169
- <li>Sort: ${this.sortParam?.field} ${this.sortParam?.direction}</li>
170
- <li>Full Query: ${this.fullQuery}</li>
171
- </ul>
172
- </div>
173
-
174
- <div id="content-container">
175
- <div id="left-column">
176
- <div id="histogram-container">${this.histogramTemplate}</div>
177
- <div id="facets-container">
178
- ${this.facetsLoading ? this.loadingTemplate : nothing}
179
- <collection-facets
180
- @facetsChanged=${this.facetsChanged}
181
- .aggregations=${this.aggregations}
182
- ></collection-facets>
223
+ <div id="content-container" class=${this.mobileView ? 'mobile' : ''}>
224
+ <div id="left-column" class="column">
225
+ <div id="mobile-header-container">
226
+ ${this.mobileView
227
+ ? html`
228
+ <div id="mobile-filter-collapse">
229
+ <h1
230
+ @click=${() => {
231
+ this.mobileFacetsVisible = !this.mobileFacetsVisible;
232
+ }}
233
+ @keyup=${() => {
234
+ this.mobileFacetsVisible = !this.mobileFacetsVisible;
235
+ }}
236
+ >
237
+ <span
238
+ class="collapser ${this.mobileFacetsVisible
239
+ ? 'open'
240
+ : ''}"
241
+ >
242
+ ${chevronIcon}
243
+ </span>
244
+ Filters
245
+ </h1>
246
+ </div>
247
+ `
248
+ : nothing}
249
+ <div id="results-total">
250
+ <span id="big-results-count"
251
+ >${this.totalResults
252
+ ? this.totalResults.toLocaleString()
253
+ : '-'}</span
254
+ >
255
+ <span id="big-results-label">Results</span>
256
+ </div>
257
+ </div>
258
+ <div
259
+ id="facets-container"
260
+ class=${!this.mobileView || this.mobileFacetsVisible
261
+ ? 'expanded'
262
+ : ''}
263
+ >
264
+ ${this.facetsTemplate}
183
265
  </div>
184
266
  </div>
185
- <div id="right-column">
267
+ <div id="right-column" class="column">
186
268
  ${this.searchResultsLoading ? this.loadingTemplate : nothing}
269
+ <sort-filter-bar
270
+ .selectedSort=${this.selectedSort}
271
+ .sortDirection=${this.sortDirection}
272
+ .displayMode=${this.displayMode}
273
+ .selectedTitleFilter=${this.selectedTitleFilter}
274
+ .selectedCreatorFilter=${this.selectedCreatorFilter}
275
+ .resizeObserver=${this.resizeObserver}
276
+ @sortChanged=${this.userChangedSort}
277
+ @displayModeChanged=${this.displayModeChanged}
278
+ @titleLetterChanged=${this.titleLetterSelected}
279
+ @creatorLetterChanged=${this.creatorLetterSelected}
280
+ ></sort-filter-bar>
281
+
282
+ ${this.displayMode === `list-compact`
283
+ ? this.listHeaderTemplate
284
+ : nothing}
285
+
187
286
  <infinite-scroller
188
- class="${this.displayMode}"
287
+ class="${ifDefined(this.displayMode)}"
189
288
  .cellProvider=${this}
190
289
  .placeholderCellTemplate=${this.placeholderCellTemplate}
191
290
  @scrollThresholdReached=${this.scrollThresholdReached}
@@ -197,6 +296,81 @@ export class CollectionBrowser
197
296
  `;
198
297
  }
199
298
 
299
+ private userChangedSort(
300
+ e: CustomEvent<{
301
+ selectedSort: SortField;
302
+ sortDirection: SortDirection | null;
303
+ }>
304
+ ) {
305
+ const { selectedSort, sortDirection } = e.detail;
306
+ this.selectedSort = selectedSort;
307
+ this.sortDirection = sortDirection;
308
+
309
+ if ((this.currentPage ?? 1) > 1) {
310
+ this.goToPage(1);
311
+ }
312
+ this.currentPage = 1;
313
+ }
314
+
315
+ private selectedSortChanged() {
316
+ if (this.selectedSort === 'relevance' || this.sortDirection === null) {
317
+ this.sortParam = null;
318
+ return;
319
+ }
320
+ const sortField = SortFieldToMetadataField[this.selectedSort];
321
+ if (!sortField) return;
322
+ this.sortParam = new SortParam(sortField, this.sortDirection);
323
+ }
324
+
325
+ private displayModeChanged(
326
+ e: CustomEvent<{ displayMode: CollectionDisplayMode }>
327
+ ) {
328
+ this.displayMode = e.detail.displayMode;
329
+ }
330
+
331
+ private selectedTitleLetterChanged() {
332
+ this.titleQuery = this.selectedTitleFilter
333
+ ? `firstTitle:${this.selectedTitleFilter}`
334
+ : undefined;
335
+ }
336
+
337
+ private selectedCreatorLetterChanged() {
338
+ this.creatorQuery = this.selectedCreatorFilter
339
+ ? `firstCreator:${this.selectedCreatorFilter}`
340
+ : undefined;
341
+ }
342
+
343
+ private titleLetterSelected(e: CustomEvent<{ selectedLetter: string }>) {
344
+ this.selectedTitleFilter = e.detail.selectedLetter;
345
+ }
346
+
347
+ private creatorLetterSelected(e: CustomEvent<{ selectedLetter: string }>) {
348
+ this.selectedCreatorFilter = e.detail.selectedLetter;
349
+ }
350
+
351
+ private get facetDataLoading(): boolean {
352
+ return this.facetsLoading || this.fullYearAggregationLoading;
353
+ }
354
+
355
+ private get facetsTemplate() {
356
+ return html`
357
+ ${this.facetsLoading ? this.loadingTemplate : nothing}
358
+ <collection-facets
359
+ @facetsChanged=${this.facetsChanged}
360
+ @histogramDateRangeUpdated=${this.histogramDateRangeUpdated}
361
+ .aggregations=${this.aggregations}
362
+ .fullYearsHistogramAggregation=${this.fullYearsHistogramAggregation}
363
+ .minSelectedDate=${this.minSelectedDate}
364
+ .maxSelectedDate=${this.maxSelectedDate}
365
+ .selectedFacets=${this.selectedFacets}
366
+ .collectionNameCache=${this.collectionNameCache}
367
+ ?collapsableFacets=${this.mobileView}
368
+ ?facetsLoading=${this.facetDataLoading}
369
+ ?fullYearAggregationLoading=${this.fullYearAggregationLoading}
370
+ ></collection-facets>
371
+ `;
372
+ }
373
+
200
374
  private get loadingTemplate() {
201
375
  return html`
202
376
  <div class="loading-cover">
@@ -205,30 +379,31 @@ export class CollectionBrowser
205
379
  `;
206
380
  }
207
381
 
208
- private get currentYearsHistogramAggregation(): Aggregation | undefined {
209
- return this.aggregations?.year_histogram;
210
- }
211
-
212
- private get histogramDataLoading(): boolean {
213
- return this.facetsLoading || this.fullYearAggregationLoading;
382
+ private get listHeaderTemplate() {
383
+ return html`
384
+ <div id="list-header">
385
+ <tile-dispatcher
386
+ .displayMode=${'list-header'}
387
+ .resizeObserver=${this.resizeObserver}
388
+ .sortParam=${this.sortParam}
389
+ >
390
+ </tile-dispatcher>
391
+ </div>
392
+ `;
214
393
  }
215
394
 
216
- private get histogramTemplate() {
217
- const { currentYearsHistogramAggregation, fullYearsHistogramAggregation } =
218
- this;
395
+ private get queryDebuggingTemplate() {
219
396
  return html`
220
- <histogram-date-range
221
- .minDate=${fullYearsHistogramAggregation?.first_bucket_key}
222
- .maxDate=${fullYearsHistogramAggregation?.last_bucket_key}
223
- .minSelectedDate=${currentYearsHistogramAggregation?.first_bucket_key}
224
- .maxSelectedDate=${currentYearsHistogramAggregation?.last_bucket_key}
225
- .updateDelay=${100}
226
- missingDataMessage="..."
227
- ?loading=${this.histogramDataLoading}
228
- .width=${150}
229
- .bins=${fullYearsHistogramAggregation?.buckets}
230
- @histogramDateRangeUpdated=${this.histogramDateRangeUpdated}
231
- ></histogram-date-range>
397
+ <div>
398
+ <ul>
399
+ <li>Base Query: ${this.baseQuery}</li>
400
+ <li>Facet Query: ${this.facetQuery}</li>
401
+ <li>Sort Filter Query: ${this.sortFilterQueries}</li>
402
+ <li>Date Range Query: ${this.dateRangeQueryClause}</li>
403
+ <li>Sort: ${this.sortParam?.field} ${this.sortParam?.direction}</li>
404
+ <li>Full Query: ${this.fullQuery}</li>
405
+ </ul>
406
+ </div>
232
407
  `;
233
408
  }
234
409
 
@@ -242,6 +417,10 @@ export class CollectionBrowser
242
417
  this.dateRangeQueryClause = `year:[${minDate} TO ${maxDate}]`;
243
418
  }
244
419
 
420
+ firstUpdated(): void {
421
+ this.restoreState();
422
+ }
423
+
245
424
  updated(changed: PropertyValues) {
246
425
  if (
247
426
  changed.has('displayMode') ||
@@ -250,9 +429,13 @@ export class CollectionBrowser
250
429
  ) {
251
430
  this.infiniteScroller.reload();
252
431
  }
432
+ if (changed.has('currentPage') || changed.has('displayMode')) {
433
+ this.persistState();
434
+ }
253
435
  if (
254
436
  changed.has('baseQuery') ||
255
- changed.has('additionalQueryClause') ||
437
+ changed.has('titleQuery') ||
438
+ changed.has('creatorQuery') ||
256
439
  changed.has('dateRangeQueryClause') ||
257
440
  changed.has('sortParam') ||
258
441
  changed.has('selectedFacets') ||
@@ -260,11 +443,56 @@ export class CollectionBrowser
260
443
  ) {
261
444
  this.handleQueryChange();
262
445
  }
446
+ if (changed.has('selectedSort') || changed.has('sortDirection')) {
447
+ this.selectedSortChanged();
448
+ }
449
+ if (changed.has('selectedTitleFilter')) {
450
+ this.selectedTitleLetterChanged();
451
+ }
452
+ if (changed.has('selectedCreatorFilter')) {
453
+ this.selectedCreatorLetterChanged();
454
+ }
263
455
  if (changed.has('pagesToRender')) {
264
456
  if (!this.endOfDataReached) {
265
457
  this.infiniteScroller.itemCount = this.estimatedTileCount;
266
458
  }
267
459
  }
460
+ if (changed.has('resizeObserver')) {
461
+ const oldObserver = changed.get(
462
+ 'resizeObserver'
463
+ ) as SharedResizeObserverInterface;
464
+ if (oldObserver) this.disconnectResizeObserver(oldObserver);
465
+ this.setupResizeObserver();
466
+ }
467
+ }
468
+
469
+ disconnectedCallback(): void {
470
+ if (this.resizeObserver) {
471
+ this.disconnectResizeObserver(this.resizeObserver);
472
+ }
473
+ }
474
+
475
+ handleResize(entry: ResizeObserverEntry): void {
476
+ if (entry.target === this.contentContainer) {
477
+ this.mobileView = entry.contentRect.width < 600;
478
+ }
479
+ }
480
+
481
+ private disconnectResizeObserver(
482
+ resizeObserver: SharedResizeObserverInterface
483
+ ) {
484
+ resizeObserver.removeObserver({
485
+ target: this.contentContainer,
486
+ handler: this,
487
+ });
488
+ }
489
+
490
+ private setupResizeObserver() {
491
+ if (!this.resizeObserver) return;
492
+ this.resizeObserver.addObserver({
493
+ target: this.contentContainer,
494
+ handler: this,
495
+ });
268
496
  }
269
497
 
270
498
  /**
@@ -284,6 +512,9 @@ export class CollectionBrowser
284
512
  visibleCellIndices[visibleCellIndices.length - 1];
285
513
  const lastVisibleCellPage =
286
514
  Math.floor(lastVisibleCellIndex / this.pageSize) + 1;
515
+ if (this.currentPage !== lastVisibleCellPage) {
516
+ this.currentPage = lastVisibleCellPage;
517
+ }
287
518
  const event = new CustomEvent('visiblePageChanged', {
288
519
  detail: {
289
520
  pageNumber: lastVisibleCellPage,
@@ -301,7 +532,8 @@ export class CollectionBrowser
301
532
 
302
533
  private async handleQueryChange() {
303
534
  // only reset if the query has actually changed
304
- if (this.pageFetchQueryKey === this.previousQueryKey) return;
535
+ if (!this.searchService || this.pageFetchQueryKey === this.previousQueryKey)
536
+ return;
305
537
  this.previousQueryKey = this.pageFetchQueryKey;
306
538
 
307
539
  this.dataSource = {};
@@ -312,6 +544,7 @@ export class CollectionBrowser
312
544
  this.scrollToPage(this.initialPageNumber);
313
545
  }
314
546
  this.initialQueryChangeHappened = true;
547
+ this.persistState();
315
548
 
316
549
  await Promise.all([
317
550
  this.doInitialPageFetch(),
@@ -320,6 +553,47 @@ export class CollectionBrowser
320
553
  ]);
321
554
  }
322
555
 
556
+ private restoreState() {
557
+ const restorationState = this.restorationStateHandler.getRestorationState();
558
+ this.displayMode = restorationState.displayMode;
559
+ this.selectedSort = restorationState.selectedSort ?? SortField.relevance;
560
+ this.sortDirection = restorationState.sortDirection ?? null;
561
+ this.selectedTitleFilter = restorationState.selectedTitleFilter ?? null;
562
+ this.selectedCreatorFilter = restorationState.selectedCreatorFilter ?? null;
563
+ this.selectedFacets = restorationState.selectedFacets;
564
+ this.baseQuery = restorationState.baseQuery;
565
+ this.titleQuery = restorationState.titleQuery;
566
+ this.creatorQuery = restorationState.creatorQuery;
567
+ this.dateRangeQueryClause = restorationState.dateRangeQueryClause;
568
+ this.sortParam = restorationState.sortParam ?? null;
569
+ this.currentPage = restorationState.currentPage ?? 1;
570
+ this.minSelectedDate = restorationState.minSelectedDate;
571
+ this.maxSelectedDate = restorationState.maxSelectedDate;
572
+ if (this.currentPage > 1) {
573
+ this.goToPage(this.currentPage);
574
+ }
575
+ }
576
+
577
+ private persistState() {
578
+ const restorationState: RestorationState = {
579
+ displayMode: this.displayMode,
580
+ sortParam: this.sortParam ?? undefined,
581
+ selectedSort: this.selectedSort,
582
+ sortDirection: this.sortDirection ?? undefined,
583
+ selectedFacets: this.selectedFacets ?? defaultSelectedFacets,
584
+ baseQuery: this.baseQuery,
585
+ currentPage: this.currentPage,
586
+ dateRangeQueryClause: this.dateRangeQueryClause,
587
+ titleQuery: this.titleQuery,
588
+ creatorQuery: this.creatorQuery,
589
+ minSelectedDate: this.minSelectedDate,
590
+ maxSelectedDate: this.maxSelectedDate,
591
+ selectedTitleFilter: this.selectedTitleFilter ?? undefined,
592
+ selectedCreatorFilter: this.selectedCreatorFilter ?? undefined,
593
+ };
594
+ this.restorationStateHandler.persistState(restorationState);
595
+ }
596
+
323
597
  private async doInitialPageFetch() {
324
598
  this.searchResultsLoading = true;
325
599
  await this.fetchPage(this.initialPageNumber);
@@ -338,32 +612,41 @@ export class CollectionBrowser
338
612
  private get fullQueryWithoutDate(): string | undefined {
339
613
  if (!this.baseQuery) return undefined;
340
614
  let fullQuery = this.baseQuery;
341
- const { facetQuery, additionalQueryClause } = this;
615
+ const { facetQuery, sortFilterQueries } = this;
342
616
  if (facetQuery) {
343
617
  fullQuery += ` AND ${facetQuery}`;
344
618
  }
345
- if (additionalQueryClause) {
346
- fullQuery += ` AND ${additionalQueryClause}`;
619
+ if (sortFilterQueries) {
620
+ fullQuery += ` AND ${sortFilterQueries}`;
347
621
  }
348
622
  return fullQuery;
349
623
  }
350
624
 
625
+ /**
626
+ * Generates a query string for the given facets
627
+ *
628
+ * Example: `mediatype:("collection" OR "audio" OR -"etree") AND year:("2000" OR "2001")`
629
+ */
351
630
  private get facetQuery(): string | undefined {
631
+ if (!this.selectedFacets) return undefined;
352
632
  const facetQuery = [];
353
- for (const [facetName, selectedValues] of Object.entries(
633
+ for (const [facetName, facetValues] of Object.entries(
354
634
  this.selectedFacets
355
635
  )) {
356
- const values: string[] = [];
357
- for (const value of selectedValues) {
358
- values.push(`${facetName}:"${value}"`);
636
+ const facetEntries = Object.entries(facetValues);
637
+ // eslint-disable-next-line no-continue
638
+ if (facetEntries.length === 0) continue;
639
+ const facetValuesArray: string[] = [];
640
+ for (const [key, facetState] of facetEntries) {
641
+ facetValuesArray.push(`${facetState === 'hidden' ? '-' : ''}"${key}"`);
359
642
  }
360
- const valueQuery = values.join(' OR ');
361
- facetQuery.push(`(${valueQuery})`);
643
+ const valueQuery = facetValuesArray.join(` OR `);
644
+ facetQuery.push(`${facetName}:(${valueQuery})`);
362
645
  }
363
646
  return facetQuery.length > 0 ? `(${facetQuery.join(' AND ')})` : undefined;
364
647
  }
365
648
 
366
- facetsChanged(e: CustomEvent<Record<string, string[]>>) {
649
+ facetsChanged(e: CustomEvent<SelectedFacets>) {
367
650
  this.selectedFacets = e.detail;
368
651
  }
369
652
 
@@ -517,23 +800,25 @@ export class CollectionBrowser
517
800
  const params = new SearchParams({
518
801
  query: this.fullQuery,
519
802
  fields: [
803
+ 'addeddate',
804
+ 'avg_rating',
805
+ 'collections_raw',
806
+ 'creator',
807
+ 'date',
808
+ 'description',
809
+ 'downloads',
520
810
  'identifier',
521
- 'title',
811
+ 'issue',
812
+ 'item_count',
522
813
  'mediatype',
523
- 'downloads',
524
- 'avg_rating',
525
814
  'num_favorites',
526
815
  'num_reviews',
527
- 'item_count',
528
- 'description',
529
- 'date',
530
- 'addeddate',
531
816
  'publicdate',
532
817
  'reviewdate',
533
- 'creator',
534
- 'subject', // topic
535
818
  'source',
536
- 'collection',
819
+ 'subject', // topic
820
+ 'title',
821
+ 'volume',
537
822
  ],
538
823
  page: pageNumber,
539
824
  rows: this.pageSize,
@@ -544,6 +829,8 @@ export class CollectionBrowser
544
829
 
545
830
  if (!success) return;
546
831
 
832
+ this.totalResults = success.response.numFound;
833
+
547
834
  // this is checking to see if the query has changed since the data was fetched
548
835
  // if so, we just want to discard the data since there should be a new query
549
836
  // right behind it
@@ -555,6 +842,7 @@ export class CollectionBrowser
555
842
 
556
843
  const { docs } = success.response;
557
844
  if (docs && docs.length > 0) {
845
+ this.preloadCollectionNames(docs);
558
846
  this.updateDataSource(pageNumber, docs);
559
847
  }
560
848
  if (docs.length < this.pageSize) {
@@ -566,6 +854,12 @@ export class CollectionBrowser
566
854
  this.searchResultsLoading = false;
567
855
  }
568
856
 
857
+ private preloadCollectionNames(docs: Metadata[]) {
858
+ const collectionIds = docs.map(doc => doc.collections_raw?.values).flat();
859
+ const collectionIdsArray = Array.from(new Set(collectionIds)) as string[];
860
+ this.collectionNameCache?.preloadIdentifiers(collectionIdsArray);
861
+ }
862
+
569
863
  /**
570
864
  * This is useful for determining whether we need to reload the scroller.
571
865
  *
@@ -597,23 +891,30 @@ export class CollectionBrowser
597
891
  docs?.forEach(doc => {
598
892
  if (!doc.identifier) return;
599
893
  tiles.push({
600
- identifier: doc.identifier,
601
- title: this.etreeTitle(doc.title?.value, doc.mediatype?.value),
602
- mediatype: doc.mediatype?.value ?? 'data',
603
- viewCount: doc.downloads?.value ?? 0,
604
- favCount: doc.num_favorites?.value ?? 0,
894
+ averageRating: doc.avg_rating?.value,
895
+ collections: doc.collections_raw?.values ?? [],
605
896
  commentCount: doc.num_reviews?.value ?? 0,
606
- itemCount: doc.item_count?.value ?? 0,
607
- description: doc.description?.value,
897
+ creator: doc.creator?.value,
898
+ creators: doc.creator?.values ?? [],
608
899
  dateAdded: doc.addeddate?.value,
609
900
  dateArchived: doc.publicdate?.value,
610
- dateReviewed: doc.reviewdate?.value,
611
901
  datePublished: doc.date?.value,
612
- creator: doc.creator?.value,
613
- averageRating: doc.avg_rating?.value,
614
- subject: doc.subject?.value,
902
+ dateReviewed: doc.reviewdate?.value,
903
+ description: doc.description?.value,
904
+ favCount: doc.num_favorites?.value ?? 0,
905
+ identifier: doc.identifier,
906
+ issue: doc.issue?.value,
907
+ itemCount: doc.item_count?.value ?? 0,
908
+ mediatype: doc.mediatype?.value ?? 'data',
615
909
  source: doc.source?.value,
616
- collection: doc.collection?.values ?? [],
910
+ subjects: doc.subject?.values ?? [],
911
+ title: this.etreeTitle(
912
+ doc.title?.value,
913
+ doc.mediatype?.value,
914
+ doc.collection?.values
915
+ ),
916
+ volume: doc.volume?.value,
917
+ viewCount: doc.downloads?.value ?? 0,
617
918
  });
618
919
  });
619
920
  datasource[pageNumber] = tiles;
@@ -634,10 +935,10 @@ export class CollectionBrowser
634
935
  */
635
936
  private etreeTitle(
636
937
  title: string | undefined,
637
- mediatype: string | undefined
938
+ mediatype: string | undefined,
939
+ collections: string[] | undefined
638
940
  ): string {
639
- if (mediatype === 'etree') {
640
- // || collections.includes('etree')) {
941
+ if (mediatype === 'etree' || collections?.includes('etree')) {
641
942
  const regex = /^(.*) Live at (.*) on (\d\d\d\d-\d\d-\d\d)$/;
642
943
  const newTitle = title?.replace(regex, '$3: $2');
643
944
  if (newTitle) {
@@ -657,6 +958,7 @@ export class CollectionBrowser
657
958
  .displayMode=${this.displayMode}
658
959
  .resizeObserver=${this.resizeObserver}
659
960
  .sortParam=${this.sortParam}
961
+ .collectionNameCache=${this.collectionNameCache}
660
962
  ?showDeleteButton=${this.showDeleteButtons}
661
963
  ></tile-dispatcher>`;
662
964
  }
@@ -679,17 +981,99 @@ export class CollectionBrowser
679
981
  display: flex;
680
982
  }
681
983
 
984
+ .collapser {
985
+ display: inline-block;
986
+ }
987
+
988
+ .collapser svg {
989
+ width: 10px;
990
+ height: 10px;
991
+ transition: transform 0.2s ease-out;
992
+ }
993
+
994
+ .collapser.open svg {
995
+ transform: rotate(90deg);
996
+ }
997
+
998
+ #mobile-filter-collapse h1 {
999
+ cursor: pointer;
1000
+ }
1001
+
1002
+ #content-container.mobile {
1003
+ display: block;
1004
+ }
1005
+
1006
+ .column {
1007
+ padding-top: 2rem;
1008
+ }
1009
+
682
1010
  #right-column {
683
1011
  flex: 1;
684
1012
  position: relative;
1013
+ border-left: 1px solid rgb(232, 232, 232);
1014
+ padding-left: 1rem;
1015
+ }
1016
+
1017
+ .mobile #right-column {
1018
+ border-left: none;
1019
+ padding: 0;
685
1020
  }
686
1021
 
687
1022
  #left-column {
688
- width: 15rem;
1023
+ width: 18rem;
1024
+ padding-right: 12px;
1025
+ padding-right: 1rem;
1026
+ }
1027
+
1028
+ .mobile #left-column {
1029
+ width: 100%;
1030
+ padding: 0;
1031
+ }
1032
+
1033
+ #mobile-header-container {
1034
+ display: flex;
1035
+ justify-content: space-between;
689
1036
  }
690
1037
 
691
1038
  #facets-container {
692
1039
  position: relative;
1040
+ max-height: 0;
1041
+ transition: max-height 0.2s ease-in-out;
1042
+ z-index: 1;
1043
+ }
1044
+
1045
+ .mobile #facets-container {
1046
+ overflow: hidden;
1047
+ }
1048
+
1049
+ #facets-container.expanded {
1050
+ max-height: 2000px;
1051
+ }
1052
+
1053
+ #results-total {
1054
+ display: flex;
1055
+ align-items: center;
1056
+ margin-bottom: 5rem;
1057
+ }
1058
+
1059
+ .mobile #results-total {
1060
+ margin-bottom: 0;
1061
+ }
1062
+
1063
+ #big-results-count {
1064
+ font-size: 2.4rem;
1065
+ font-weight: 500;
1066
+ margin-right: 5px;
1067
+ }
1068
+
1069
+ #big-results-label {
1070
+ font-size: 1rem;
1071
+ font-weight: 200;
1072
+ text-transform: uppercase;
1073
+ }
1074
+
1075
+ #list-header {
1076
+ max-height: 3rem;
693
1077
  }
694
1078
 
695
1079
  .loading-cover {
@@ -698,7 +1082,6 @@ export class CollectionBrowser
698
1082
  left: 0;
699
1083
  width: 100%;
700
1084
  height: 100%;
701
- background-color: rgba(255, 255, 255, 0.75);
702
1085
  display: flex;
703
1086
  justify-content: center;
704
1087
  z-index: 1;
@@ -710,6 +1093,11 @@ export class CollectionBrowser
710
1093
  height: 30px;
711
1094
  }
712
1095
 
1096
+ sort-filter-bar {
1097
+ display: block;
1098
+ margin-bottom: 4rem;
1099
+ }
1100
+
713
1101
  infinite-scroller {
714
1102
  display: block;
715
1103
  --infiniteScrollerRowGap: var(--collectionBrowserRowGap, 1.7rem);
@@ -721,14 +1109,9 @@ export class CollectionBrowser
721
1109
  --collectionBrowserCellMinWidth,
722
1110
  100%
723
1111
  );
724
- --infiniteScrollerCellMinHeight: var(
725
- --collectionBrowserCellMinHeight,
726
- 2rem
727
- );
728
- --infiniteScrollerCellMaxHeight: var(
729
- --collectionBrowserCellMaxHeight,
730
- 2rem
731
- );
1112
+ --infiniteScrollerCellMinHeight: 34px; /* override infinite scroller component */
1113
+ --infiniteScrollerCellMaxHeight: 56px;
1114
+ --infiniteScrollerRowGap: 0px;
732
1115
  }
733
1116
 
734
1117
  infinite-scroller.list-detail {
@@ -740,6 +1123,8 @@ export class CollectionBrowser
740
1123
  --collectionBrowserCellMinHeight,
741
1124
  5rem
742
1125
  );
1126
+ /* 30px, but compensating for a -5px margin */
1127
+ --infiniteScrollerRowGap: 35px;
743
1128
  }
744
1129
 
745
1130
  infinite-scroller.grid {