@internetarchive/collection-browser 1.14.17-beta.5 → 1.14.18-alpha.0

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 (352) hide show
  1. package/.editorconfig +29 -29
  2. package/.husky/pre-commit +4 -4
  3. package/LICENSE +661 -661
  4. package/README.md +83 -83
  5. package/dist/index.d.ts +12 -11
  6. package/dist/index.js +12 -11
  7. package/dist/index.js.map +1 -1
  8. package/dist/src/app-root.d.ts +83 -83
  9. package/dist/src/app-root.js +438 -489
  10. package/dist/src/app-root.js.map +1 -1
  11. package/dist/src/assets/img/icons/arrow-left.d.ts +2 -2
  12. package/dist/src/assets/img/icons/arrow-left.js +2 -2
  13. package/dist/src/assets/img/icons/arrow-right.d.ts +2 -2
  14. package/dist/src/assets/img/icons/arrow-right.js +2 -2
  15. package/dist/src/assets/img/icons/chevron.d.ts +2 -2
  16. package/dist/src/assets/img/icons/chevron.js +2 -2
  17. package/dist/src/assets/img/icons/contract.d.ts +2 -2
  18. package/dist/src/assets/img/icons/contract.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/expand.d.ts +2 -2
  22. package/dist/src/assets/img/icons/expand.js +2 -2
  23. package/dist/src/assets/img/icons/eye-closed.d.ts +2 -2
  24. package/dist/src/assets/img/icons/eye-closed.js +2 -2
  25. package/dist/src/assets/img/icons/eye.d.ts +2 -2
  26. package/dist/src/assets/img/icons/eye.js +2 -2
  27. package/dist/src/assets/img/icons/favorite-filled.d.ts +1 -1
  28. package/dist/src/assets/img/icons/favorite-filled.js +2 -2
  29. package/dist/src/assets/img/icons/login-required.d.ts +1 -1
  30. package/dist/src/assets/img/icons/login-required.js +2 -2
  31. package/dist/src/assets/img/icons/mediatype/account.d.ts +1 -1
  32. package/dist/src/assets/img/icons/mediatype/account.js +2 -2
  33. package/dist/src/assets/img/icons/mediatype/audio.d.ts +1 -1
  34. package/dist/src/assets/img/icons/mediatype/audio.js +2 -2
  35. package/dist/src/assets/img/icons/mediatype/collection.d.ts +1 -1
  36. package/dist/src/assets/img/icons/mediatype/collection.js +2 -2
  37. package/dist/src/assets/img/icons/mediatype/data.d.ts +1 -1
  38. package/dist/src/assets/img/icons/mediatype/data.js +2 -2
  39. package/dist/src/assets/img/icons/mediatype/etree.d.ts +1 -1
  40. package/dist/src/assets/img/icons/mediatype/etree.js +2 -2
  41. package/dist/src/assets/img/icons/mediatype/film.d.ts +1 -1
  42. package/dist/src/assets/img/icons/mediatype/film.js +2 -2
  43. package/dist/src/assets/img/icons/mediatype/images.d.ts +1 -1
  44. package/dist/src/assets/img/icons/mediatype/images.js +2 -2
  45. package/dist/src/assets/img/icons/mediatype/radio.d.ts +1 -1
  46. package/dist/src/assets/img/icons/mediatype/radio.js +2 -2
  47. package/dist/src/assets/img/icons/mediatype/search.d.ts +1 -1
  48. package/dist/src/assets/img/icons/mediatype/search.js +2 -2
  49. package/dist/src/assets/img/icons/mediatype/software.d.ts +1 -1
  50. package/dist/src/assets/img/icons/mediatype/software.js +2 -2
  51. package/dist/src/assets/img/icons/mediatype/texts.d.ts +1 -1
  52. package/dist/src/assets/img/icons/mediatype/texts.js +2 -2
  53. package/dist/src/assets/img/icons/mediatype/tv.d.ts +1 -1
  54. package/dist/src/assets/img/icons/mediatype/tv.js +2 -2
  55. package/dist/src/assets/img/icons/mediatype/video.d.ts +1 -1
  56. package/dist/src/assets/img/icons/mediatype/video.js +2 -2
  57. package/dist/src/assets/img/icons/mediatype/web.d.ts +1 -1
  58. package/dist/src/assets/img/icons/mediatype/web.js +2 -2
  59. package/dist/src/assets/img/icons/null-result.d.ts +2 -2
  60. package/dist/src/assets/img/icons/null-result.js +2 -2
  61. package/dist/src/assets/img/icons/restricted.d.ts +1 -1
  62. package/dist/src/assets/img/icons/restricted.js +2 -2
  63. package/dist/src/assets/img/icons/reviews.d.ts +1 -1
  64. package/dist/src/assets/img/icons/reviews.js +2 -2
  65. package/dist/src/assets/img/icons/upload.d.ts +1 -1
  66. package/dist/src/assets/img/icons/upload.js +2 -2
  67. package/dist/src/assets/img/icons/views.d.ts +1 -1
  68. package/dist/src/assets/img/icons/views.js +2 -2
  69. package/dist/src/circular-activity-indicator.d.ts +5 -5
  70. package/dist/src/circular-activity-indicator.js +17 -17
  71. package/dist/src/collection-browser.d.ts +384 -406
  72. package/dist/src/collection-browser.js +1342 -1363
  73. package/dist/src/collection-browser.js.map +1 -1
  74. package/dist/src/collection-facets/facet-row.d.ts +30 -30
  75. package/dist/src/collection-facets/facet-row.js +114 -114
  76. package/dist/src/collection-facets/facet-tombstone-row.d.ts +5 -5
  77. package/dist/src/collection-facets/facet-tombstone-row.js +15 -15
  78. package/dist/src/collection-facets/facets-template.d.ts +17 -17
  79. package/dist/src/collection-facets/facets-template.js +114 -114
  80. package/dist/src/collection-facets/more-facets-content.d.ts +70 -70
  81. package/dist/src/collection-facets/more-facets-content.js +354 -369
  82. package/dist/src/collection-facets/more-facets-content.js.map +1 -1
  83. package/dist/src/collection-facets/more-facets-pagination.d.ts +36 -36
  84. package/dist/src/collection-facets/more-facets-pagination.js +196 -196
  85. package/dist/src/collection-facets/toggle-switch.d.ts +41 -41
  86. package/dist/src/collection-facets/toggle-switch.js +94 -94
  87. package/dist/src/collection-facets.d.ts +103 -103
  88. package/dist/src/collection-facets.js +513 -510
  89. package/dist/src/collection-facets.js.map +1 -1
  90. package/dist/src/data-source/collection-browser-data-source-interface.d.ts +217 -0
  91. package/dist/src/data-source/collection-browser-data-source-interface.js +2 -0
  92. package/dist/src/data-source/collection-browser-data-source-interface.js.map +1 -0
  93. package/dist/src/data-source/collection-browser-data-source.d.ts +352 -434
  94. package/dist/src/data-source/collection-browser-data-source.js +911 -800
  95. package/dist/src/data-source/collection-browser-data-source.js.map +1 -1
  96. package/dist/src/data-source/collection-browser-query-state.d.ts +43 -0
  97. package/dist/src/data-source/collection-browser-query-state.js +2 -0
  98. package/dist/src/data-source/collection-browser-query-state.js.map +1 -0
  99. package/dist/src/data-source/models.d.ts +28 -62
  100. package/dist/src/data-source/models.js +8 -1
  101. package/dist/src/data-source/models.js.map +1 -1
  102. package/dist/src/empty-placeholder.d.ts +23 -23
  103. package/dist/src/empty-placeholder.js +74 -74
  104. package/dist/src/expanded-date-picker.d.ts +43 -43
  105. package/dist/src/expanded-date-picker.js +109 -109
  106. package/dist/src/language-code-handler/language-code-handler.d.ts +37 -37
  107. package/dist/src/language-code-handler/language-code-handler.js +26 -26
  108. package/dist/src/language-code-handler/language-code-mapping.d.ts +1 -1
  109. package/dist/src/language-code-handler/language-code-mapping.js +562 -562
  110. package/dist/src/manage/manage-bar.d.ts +26 -26
  111. package/dist/src/manage/manage-bar.js +53 -53
  112. package/dist/src/manage/manage-bar.js.map +1 -1
  113. package/dist/src/mediatype/mediatype-config.d.ts +3 -3
  114. package/dist/src/mediatype/mediatype-config.js +91 -91
  115. package/dist/src/models.d.ts +197 -180
  116. package/dist/src/models.js +380 -269
  117. package/dist/src/models.js.map +1 -1
  118. package/dist/src/restoration-state-handler.d.ts +70 -70
  119. package/dist/src/restoration-state-handler.js +355 -355
  120. package/dist/src/sort-filter-bar/alpha-bar-tooltip.d.ts +6 -6
  121. package/dist/src/sort-filter-bar/alpha-bar-tooltip.js +24 -24
  122. package/dist/src/sort-filter-bar/alpha-bar.d.ts +21 -21
  123. package/dist/src/sort-filter-bar/alpha-bar.js +128 -128
  124. package/dist/src/sort-filter-bar/img/compact.d.ts +1 -1
  125. package/dist/src/sort-filter-bar/img/compact.js +2 -2
  126. package/dist/src/sort-filter-bar/img/list.d.ts +1 -1
  127. package/dist/src/sort-filter-bar/img/list.js +2 -2
  128. package/dist/src/sort-filter-bar/img/sort-toggle-disabled.d.ts +1 -1
  129. package/dist/src/sort-filter-bar/img/sort-toggle-disabled.js +2 -2
  130. package/dist/src/sort-filter-bar/img/sort-toggle-down.d.ts +1 -1
  131. package/dist/src/sort-filter-bar/img/sort-toggle-down.js +2 -2
  132. package/dist/src/sort-filter-bar/img/sort-toggle-up.d.ts +1 -1
  133. package/dist/src/sort-filter-bar/img/sort-toggle-up.js +2 -2
  134. package/dist/src/sort-filter-bar/img/sort-triangle.d.ts +1 -1
  135. package/dist/src/sort-filter-bar/img/sort-triangle.js +2 -2
  136. package/dist/src/sort-filter-bar/img/tile.d.ts +1 -1
  137. package/dist/src/sort-filter-bar/img/tile.js +2 -2
  138. package/dist/src/sort-filter-bar/sort-filter-bar.d.ts +210 -210
  139. package/dist/src/sort-filter-bar/sort-filter-bar.js +653 -652
  140. package/dist/src/sort-filter-bar/sort-filter-bar.js.map +1 -1
  141. package/dist/src/styles/item-image-styles.d.ts +8 -8
  142. package/dist/src/styles/item-image-styles.js +9 -9
  143. package/dist/src/styles/sr-only.d.ts +1 -1
  144. package/dist/src/styles/sr-only.js +2 -2
  145. package/dist/src/tiles/base-tile-component.d.ts +19 -19
  146. package/dist/src/tiles/base-tile-component.js +63 -63
  147. package/dist/src/tiles/collection-browser-loading-tile.d.ts +5 -5
  148. package/dist/src/tiles/collection-browser-loading-tile.js +15 -15
  149. package/dist/src/tiles/grid/account-tile.d.ts +18 -18
  150. package/dist/src/tiles/grid/account-tile.js +72 -72
  151. package/dist/src/tiles/grid/collection-tile.d.ts +15 -15
  152. package/dist/src/tiles/grid/collection-tile.js +80 -80
  153. package/dist/src/tiles/grid/item-tile.d.ts +28 -27
  154. package/dist/src/tiles/grid/item-tile.js +162 -135
  155. package/dist/src/tiles/grid/item-tile.js.map +1 -1
  156. package/dist/src/tiles/grid/search-tile.d.ts +10 -10
  157. package/dist/src/tiles/grid/search-tile.js +51 -51
  158. package/dist/src/tiles/grid/styles/tile-grid-shared-styles.d.ts +1 -1
  159. package/dist/src/tiles/grid/styles/tile-grid-shared-styles.js +8 -8
  160. package/dist/src/tiles/grid/tile-stats.d.ts +11 -11
  161. package/dist/src/tiles/grid/tile-stats.js +58 -53
  162. package/dist/src/tiles/grid/tile-stats.js.map +1 -1
  163. package/dist/src/tiles/hover/hover-pane-controller.d.ts +219 -219
  164. package/dist/src/tiles/hover/hover-pane-controller.js +352 -352
  165. package/dist/src/tiles/hover/tile-hover-pane.d.ts +15 -15
  166. package/dist/src/tiles/hover/tile-hover-pane.js +38 -38
  167. package/dist/src/tiles/image-block.d.ts +17 -17
  168. package/dist/src/tiles/image-block.js +73 -73
  169. package/dist/src/tiles/item-image.d.ts +39 -36
  170. package/dist/src/tiles/item-image.js +155 -127
  171. package/dist/src/tiles/item-image.js.map +1 -1
  172. package/dist/src/tiles/list/tile-list-compact-header.d.ts +6 -6
  173. package/dist/src/tiles/list/tile-list-compact-header.js +38 -38
  174. package/dist/src/tiles/list/tile-list-compact.d.ts +15 -15
  175. package/dist/src/tiles/list/tile-list-compact.js +115 -115
  176. package/dist/src/tiles/list/tile-list-compact.js.map +1 -1
  177. package/dist/src/tiles/list/tile-list.d.ts +47 -46
  178. package/dist/src/tiles/list/tile-list.js +327 -296
  179. package/dist/src/tiles/list/tile-list.js.map +1 -1
  180. package/dist/src/tiles/mediatype-icon.d.ts +9 -9
  181. package/dist/src/tiles/mediatype-icon.js +47 -47
  182. package/dist/src/tiles/overlay/icon-overlay.d.ts +10 -10
  183. package/dist/src/tiles/overlay/icon-overlay.js +40 -40
  184. package/dist/src/tiles/overlay/icon-text-overlay.d.ts +9 -9
  185. package/dist/src/tiles/overlay/icon-text-overlay.js +38 -38
  186. package/dist/src/tiles/overlay/text-overlay.d.ts +10 -10
  187. package/dist/src/tiles/overlay/text-overlay.js +42 -42
  188. package/dist/src/tiles/text-snippet-block.d.ts +27 -27
  189. package/dist/src/tiles/text-snippet-block.js +73 -73
  190. package/dist/src/tiles/tile-dispatcher.d.ts +64 -64
  191. package/dist/src/tiles/tile-dispatcher.js +232 -231
  192. package/dist/src/tiles/tile-dispatcher.js.map +1 -1
  193. package/dist/src/tiles/tile-display-value-provider.d.ts +47 -43
  194. package/dist/src/tiles/tile-display-value-provider.js +94 -80
  195. package/dist/src/tiles/tile-display-value-provider.js.map +1 -1
  196. package/dist/src/utils/analytics-events.d.ts +25 -25
  197. package/dist/src/utils/analytics-events.js +27 -27
  198. package/dist/src/utils/array-equals.d.ts +4 -4
  199. package/dist/src/utils/array-equals.js +10 -10
  200. package/dist/src/utils/collapse-repeated-quotes.d.ts +11 -0
  201. package/dist/src/utils/collapse-repeated-quotes.js +14 -0
  202. package/dist/src/utils/collapse-repeated-quotes.js.map +1 -0
  203. package/dist/src/utils/format-count.d.ts +7 -7
  204. package/dist/src/utils/format-count.js +76 -76
  205. package/dist/src/utils/format-date.d.ts +2 -2
  206. package/dist/src/utils/format-date.js +25 -25
  207. package/dist/src/utils/format-unit-size.d.ts +2 -2
  208. package/dist/src/utils/format-unit-size.js +33 -33
  209. package/dist/src/utils/local-date-from-utc.d.ts +9 -9
  210. package/dist/src/utils/local-date-from-utc.js +15 -15
  211. package/dist/src/utils/log.d.ts +7 -0
  212. package/dist/src/utils/log.js +16 -0
  213. package/dist/src/utils/log.js.map +1 -0
  214. package/dist/src/utils/resolve-mediatype.d.ts +8 -0
  215. package/dist/src/utils/resolve-mediatype.js +24 -0
  216. package/dist/src/utils/resolve-mediatype.js.map +1 -0
  217. package/dist/src/utils/sha1.d.ts +2 -2
  218. package/dist/src/utils/sha1.js +8 -8
  219. package/dist/test/collection-browser.test.d.ts +1 -1
  220. package/dist/test/collection-browser.test.js +1211 -1149
  221. package/dist/test/collection-browser.test.js.map +1 -1
  222. package/dist/test/collection-facets/facet-row.test.d.ts +1 -1
  223. package/dist/test/collection-facets/facet-row.test.js +203 -203
  224. package/dist/test/collection-facets/facets-template.test.d.ts +1 -1
  225. package/dist/test/collection-facets/facets-template.test.js +105 -105
  226. package/dist/test/collection-facets/more-facets-content.test.d.ts +1 -1
  227. package/dist/test/collection-facets/more-facets-content.test.js +133 -133
  228. package/dist/test/collection-facets/more-facets-content.test.js.map +1 -1
  229. package/dist/test/collection-facets/more-facets-pagination.test.d.ts +1 -1
  230. package/dist/test/collection-facets/more-facets-pagination.test.js +117 -117
  231. package/dist/test/collection-facets/toggle-switch.test.d.ts +1 -1
  232. package/dist/test/collection-facets/toggle-switch.test.js +73 -73
  233. package/dist/test/collection-facets.test.d.ts +2 -2
  234. package/dist/test/collection-facets.test.js +653 -648
  235. package/dist/test/collection-facets.test.js.map +1 -1
  236. package/dist/test/data-source/collection-browser-data-source.test.d.ts +1 -0
  237. package/dist/test/data-source/collection-browser-data-source.test.js +80 -0
  238. package/dist/test/data-source/collection-browser-data-source.test.js.map +1 -0
  239. package/dist/test/empty-placeholder.test.d.ts +1 -1
  240. package/dist/test/empty-placeholder.test.js +63 -63
  241. package/dist/test/expanded-date-picker.test.d.ts +1 -1
  242. package/dist/test/expanded-date-picker.test.js +95 -95
  243. package/dist/test/icon-overlay.test.d.ts +1 -1
  244. package/dist/test/icon-overlay.test.js +24 -24
  245. package/dist/test/image-block.test.d.ts +1 -1
  246. package/dist/test/image-block.test.js +48 -48
  247. package/dist/test/item-image.test.d.ts +1 -1
  248. package/dist/test/item-image.test.js +85 -86
  249. package/dist/test/item-image.test.js.map +1 -1
  250. package/dist/test/manage/manage-bar.test.d.ts +1 -1
  251. package/dist/test/manage/manage-bar.test.js +72 -72
  252. package/dist/test/mediatype-config.test.d.ts +1 -1
  253. package/dist/test/mediatype-config.test.js +16 -16
  254. package/dist/test/mocks/mock-analytics-handler.d.ts +10 -10
  255. package/dist/test/mocks/mock-analytics-handler.js +15 -15
  256. package/dist/test/mocks/mock-search-responses.d.ts +24 -21
  257. package/dist/test/mocks/mock-search-responses.js +840 -709
  258. package/dist/test/mocks/mock-search-responses.js.map +1 -1
  259. package/dist/test/mocks/mock-search-service.d.ts +15 -15
  260. package/dist/test/mocks/mock-search-service.js +53 -50
  261. package/dist/test/mocks/mock-search-service.js.map +1 -1
  262. package/dist/test/restoration-state-handler.test.d.ts +1 -1
  263. package/dist/test/restoration-state-handler.test.js +270 -270
  264. package/dist/test/sort-filter-bar/alpha-bar-tooltip.test.d.ts +1 -1
  265. package/dist/test/sort-filter-bar/alpha-bar-tooltip.test.js +12 -12
  266. package/dist/test/sort-filter-bar/alpha-bar.test.d.ts +1 -1
  267. package/dist/test/sort-filter-bar/alpha-bar.test.js +73 -73
  268. package/dist/test/sort-filter-bar/sort-filter-bar.test.d.ts +1 -1
  269. package/dist/test/sort-filter-bar/sort-filter-bar.test.js +418 -418
  270. package/dist/test/text-overlay.test.d.ts +1 -1
  271. package/dist/test/text-overlay.test.js +48 -48
  272. package/dist/test/text-snippet-block.test.d.ts +1 -1
  273. package/dist/test/text-snippet-block.test.js +57 -57
  274. package/dist/test/tile-stats.test.d.ts +1 -1
  275. package/dist/test/tile-stats.test.js +95 -33
  276. package/dist/test/tile-stats.test.js.map +1 -1
  277. package/dist/test/tiles/grid/account-tile.test.d.ts +1 -1
  278. package/dist/test/tiles/grid/account-tile.test.js +76 -76
  279. package/dist/test/tiles/grid/collection-tile.test.d.ts +1 -1
  280. package/dist/test/tiles/grid/collection-tile.test.js +73 -73
  281. package/dist/test/tiles/grid/item-tile.test.d.ts +1 -1
  282. package/dist/test/tiles/grid/item-tile.test.js +298 -254
  283. package/dist/test/tiles/grid/item-tile.test.js.map +1 -1
  284. package/dist/test/tiles/grid/search-tile.test.d.ts +1 -1
  285. package/dist/test/tiles/grid/search-tile.test.js +51 -51
  286. package/dist/test/tiles/hover/hover-pane-controller.test.d.ts +1 -1
  287. package/dist/test/tiles/hover/hover-pane-controller.test.js +259 -258
  288. package/dist/test/tiles/hover/hover-pane-controller.test.js.map +1 -1
  289. package/dist/test/tiles/hover/tile-hover-pane.test.d.ts +1 -1
  290. package/dist/test/tiles/hover/tile-hover-pane.test.js +13 -13
  291. package/dist/test/tiles/list/tile-list-compact.test.d.ts +1 -1
  292. package/dist/test/tiles/list/tile-list-compact.test.js +143 -143
  293. package/dist/test/tiles/list/tile-list.test.d.ts +1 -1
  294. package/dist/test/tiles/list/tile-list.test.js +283 -239
  295. package/dist/test/tiles/list/tile-list.test.js.map +1 -1
  296. package/dist/test/tiles/tile-dispatcher.test.d.ts +1 -1
  297. package/dist/test/tiles/tile-dispatcher.test.js +106 -94
  298. package/dist/test/tiles/tile-dispatcher.test.js.map +1 -1
  299. package/dist/test/tiles/tile-display-value-provider.test.d.ts +1 -1
  300. package/dist/test/tiles/tile-display-value-provider.test.js +141 -141
  301. package/dist/test/utils/array-equals.test.d.ts +1 -1
  302. package/dist/test/utils/array-equals.test.js +26 -26
  303. package/dist/test/utils/format-count.test.d.ts +1 -1
  304. package/dist/test/utils/format-count.test.js +23 -23
  305. package/dist/test/utils/format-date.test.d.ts +1 -1
  306. package/dist/test/utils/format-date.test.js +17 -17
  307. package/dist/test/utils/format-unit-size.test.d.ts +1 -1
  308. package/dist/test/utils/format-unit-size.test.js +17 -17
  309. package/dist/test/utils/local-date-from-utc.test.d.ts +1 -1
  310. package/dist/test/utils/local-date-from-utc.test.js +26 -26
  311. package/index.ts +3 -5
  312. package/local.archive.org.cert +86 -86
  313. package/local.archive.org.key +27 -27
  314. package/package.json +3 -4
  315. package/renovate.json +6 -6
  316. package/src/app-root.ts +30 -85
  317. package/src/collection-browser.ts +112 -139
  318. package/src/collection-facets/more-facets-content.ts +0 -19
  319. package/src/collection-facets.ts +6 -4
  320. package/src/data-source/collection-browser-data-source-interface.ts +272 -0
  321. package/src/data-source/collection-browser-data-source.ts +314 -379
  322. package/src/data-source/collection-browser-query-state.ts +54 -0
  323. package/src/data-source/models.ts +8 -45
  324. package/src/manage/manage-bar.ts +1 -1
  325. package/src/models.ts +164 -3
  326. package/src/sort-filter-bar/sort-filter-bar.ts +2 -1
  327. package/src/tiles/grid/item-tile.ts +36 -1
  328. package/src/tiles/grid/tile-stats.ts +12 -7
  329. package/src/tiles/item-image.ts +28 -0
  330. package/src/tiles/list/tile-list-compact.ts +1 -1
  331. package/src/tiles/list/tile-list.ts +43 -5
  332. package/src/tiles/tile-dispatcher.ts +2 -1
  333. package/src/tiles/tile-display-value-provider.ts +20 -2
  334. package/src/utils/collapse-repeated-quotes.ts +13 -0
  335. package/src/utils/log.ts +16 -0
  336. package/src/utils/resolve-mediatype.ts +26 -0
  337. package/test/collection-browser.test.ts +161 -50
  338. package/test/collection-facets/more-facets-content.test.ts +4 -2
  339. package/test/collection-facets.test.ts +5 -0
  340. package/test/data-source/collection-browser-data-source.test.ts +103 -0
  341. package/test/item-image.test.ts +34 -36
  342. package/test/mocks/mock-search-responses.ts +144 -0
  343. package/test/mocks/mock-search-service.ts +6 -0
  344. package/test/tile-stats.test.ts +104 -0
  345. package/test/tiles/grid/item-tile.test.ts +55 -0
  346. package/test/tiles/hover/hover-pane-controller.test.ts +19 -17
  347. package/test/tiles/list/tile-list.test.ts +55 -0
  348. package/test/tiles/tile-dispatcher.test.ts +13 -0
  349. package/tsconfig.json +21 -21
  350. package/web-dev-server.config.mjs +30 -30
  351. package/web-test-runner.config.mjs +41 -41
  352. package/types/sentry.d.ts +0 -1
@@ -1,4 +1,4 @@
1
- import type { ReactiveController, ReactiveControllerHost } from 'lit';
1
+ import type { ReactiveControllerHost } from 'lit';
2
2
  import {
3
3
  AccountExtraInfo,
4
4
  Aggregation,
@@ -12,296 +12,233 @@ import {
12
12
  SearchResult,
13
13
  SearchType,
14
14
  } from '@internetarchive/search-service';
15
- import type { MediaType } from '@internetarchive/field-parsers';
16
15
  import {
17
16
  prefixFilterAggregationKeys,
18
17
  type FacetBucket,
19
18
  type PrefixFilterType,
20
- type TileModel,
19
+ TileModel,
21
20
  PrefixFilterCounts,
21
+ RequestKind,
22
+ SortField,
23
+ SORT_OPTIONS,
22
24
  } from '../models';
23
- import type {
24
- CollectionBrowserSearchInterface,
25
- CollectionTitles,
26
- PageSpecifierParams,
27
- } from './models';
25
+ import { FACETLESS_PAGE_ELEMENTS, type PageSpecifierParams } from './models';
26
+ import type { CollectionBrowserDataSourceInterface } from './collection-browser-data-source-interface';
27
+ import type { CollectionBrowserSearchInterface } from './collection-browser-query-state';
28
28
  import { sha1 } from '../utils/sha1';
29
+ import { log } from '../utils/log';
29
30
 
30
- type RequestKind = 'full' | 'hits' | 'aggregations';
31
-
32
- export interface CollectionBrowserDataSourceInterface
33
- extends ReactiveController {
34
- /**
35
- * How many tile models are present in this data source
36
- */
37
- readonly size: number;
38
-
39
- /**
40
- * Whether the host has a valid set of properties for performing a search.
41
- * For instance, on the search page this requires a valid search service and a
42
- * non-empty query, while collection pages allow searching with an empty query
43
- * for MDS but not FTS.
44
- */
45
- readonly canPerformSearch: boolean;
46
-
47
- /**
48
- * A string key compactly representing the current full search state, which can
49
- * be used to determine, e.g., when a new search is required or whether an arriving
50
- * response is outdated.
51
- */
52
- readonly pageFetchQueryKey: string;
53
-
54
- /**
55
- * Similar to `pageFetchQueryKey`, but excluding properties that do not affect
56
- * the validity of a set of facets (e.g., sort).
57
- */
58
- readonly facetFetchQueryKey: string;
59
-
60
- /**
61
- * An object representing any collection- or profile-specific properties to be passed along
62
- * to the search service, specifying the exact page/tab to fetch results for.
63
- */
64
- readonly pageSpecifierParams: PageSpecifierParams | null;
65
-
66
- /**
67
- * A FilterMap object representing all filters applied to the current search,
68
- * including any facets, letter filters, and date ranges.
69
- */
70
- readonly filterMap: FilterMap;
71
-
72
- /**
73
- * The full set of aggregations retrieved for the current search.
74
- */
75
- readonly aggregations?: Record<string, Aggregation>;
76
-
77
- /**
78
- * The `year_histogram` aggregation retrieved for the current search.
79
- */
80
- readonly yearHistogramAggregation?: Aggregation;
81
-
31
+ export class CollectionBrowserDataSource
32
+ implements CollectionBrowserDataSourceInterface
33
+ {
82
34
  /**
83
- * A map from collection identifiers that appear on hits or aggregations for the
84
- * current search, to their human-readable collection titles.
35
+ * All pages of tile models that have been fetched so far, indexed by their page
36
+ * number (with the first being page 1).
85
37
  */
86
- readonly collectionTitles: CollectionTitles;
38
+ private pages: Record<string, TileModel[]> = {};
87
39
 
88
40
  /**
89
- * The "extra info" package provided by the PPS for collection pages, including details
90
- * used to populate the target collection header & About tab content.
41
+ * Tile offset to apply when looking up tiles in the pages, in order to maintain
42
+ * page alignment after tiles are removed.
91
43
  */
92
- readonly collectionExtraInfo?: CollectionExtraInfo;
44
+ private offset = 0;
93
45
 
94
46
  /**
95
- * The "extra info" package provided by the PPS for profile pages, including details
96
- * used to populate the profile header.
47
+ * Total number of tile models stored in this data source's pages
97
48
  */
98
- readonly accountExtraInfo?: AccountExtraInfo;
49
+ private numTileModels = 0;
99
50
 
100
51
  /**
101
- * The set of requested page elements for profile pages, if applicable. These represent
102
- * any content specific to the current profile tab.
52
+ * A set of fetch IDs that are valid for the current query state
103
53
  */
104
- readonly pageElements?: PageElementMap;
54
+ private fetchesInProgress = new Set<string>();
105
55
 
106
56
  /**
107
- * An array of the current target collection's parent collections. Should include *all*
108
- * ancestors in the collection hierarchy, not just the immediate parent.
57
+ * A record of the query key used for the last search.
58
+ * If this changes, we need to load new results.
109
59
  */
110
- readonly parentCollections?: string[];
60
+ private previousQueryKey: string = '';
111
61
 
112
62
  /**
113
- * An object storing result counts for the current search bucketed by letter prefix.
114
- * Keys are the result field on which the prefixes are considered (e.g., title/creator)
115
- * and values are a Record mapping letters to their counts.
63
+ * Whether the initial page of search results for the current query state
64
+ * is presently being fetched.
116
65
  */
117
- readonly prefixFilterCountMap: Partial<
118
- Record<PrefixFilterType, PrefixFilterCounts>
119
- >;
66
+ private searchResultsLoading = false;
120
67
 
121
68
  /**
122
- * An array of all the tile models whose management checkboxes are checked
69
+ * Whether the facets (aggregations) for the current query state are
70
+ * presently being fetched.
123
71
  */
124
- readonly checkedTileModels: TileModel[];
72
+ private facetsLoading = false;
125
73
 
126
74
  /**
127
- * An array of all the tile models whose management checkboxes are unchecked
75
+ * Whether further query changes should be ignored and not trigger fetches
128
76
  */
129
- readonly uncheckedTileModels: TileModel[];
77
+ private suppressFetches = false;
130
78
 
131
79
  /**
132
- * Adds the given page of tile models to the data source.
133
- * If the given page number already exists, that page will be overwritten.
134
- * @param pageNum Which page number to add (indexed starting from 1)
135
- * @param pageTiles The array of tile models for the new page
80
+ * @inheritdoc
136
81
  */
137
- addPage(pageNum: number, pageTiles: TileModel[]): void;
82
+ totalResults = 0;
138
83
 
139
84
  /**
140
- * Returns the given page of tile models from the data source.
141
- * @param pageNum Which page number to get (indexed starting from 1)
85
+ * @inheritdoc
142
86
  */
143
- getPage(pageNum: number): TileModel[];
87
+ endOfDataReached = false;
144
88
 
145
89
  /**
146
- * Returns the full set of paged tile models stored in this data source.
90
+ * @inheritdoc
147
91
  */
148
- getAllPages(): Record<string, TileModel[]>;
92
+ queryInitialized = false;
149
93
 
150
94
  /**
151
- * Whether the data source contains any tiles for the given page number.
152
- * @param pageNum Which page number to query (indexed starting from 1)
95
+ * @inheritdoc
153
96
  */
154
- hasPage(pageNum: number): boolean;
97
+ aggregations?: Record<string, Aggregation>;
155
98
 
156
99
  /**
157
- * Returns the single tile model appearing at the given index in the
158
- * data source, with respect to the current page size. Returns `undefined` if
159
- * the corresponding page is not present on the data source or if it does not
160
- * contain a tile model at the corresponding index.
161
- * @param index The 0-based index (within the full data source) of the tile to get
100
+ * @inheritdoc
162
101
  */
163
- getTileModelAt(index: number): TileModel | undefined;
102
+ yearHistogramAggregation?: Aggregation;
164
103
 
165
104
  /**
166
- * Returns the first numeric tile index corresponding to the given tile model object,
167
- * or -1 if the given tile model is not present.
168
- * @param tile The tile model to search for in the data source
105
+ * @inheritdoc
169
106
  */
170
- indexOf(tile: TileModel): number;
107
+ collectionTitles = new Map<string, string>();
171
108
 
172
109
  /**
173
- * Requests that the data source fire a backend request for the given page of results.
174
- * @param pageNum Which page number to fetch results for
175
- * @param numInitialPages How many pages should be batched together on an initial fetch
110
+ * @inheritdoc
176
111
  */
177
- fetchPage(pageNum: number, numInitialPages?: number): Promise<void>;
112
+ collectionExtraInfo?: CollectionExtraInfo;
178
113
 
179
114
  /**
180
- * Requests that the data source update its prefix bucket result counts for the given
181
- * type of prefix filter.
182
- * @param filterType Which prefixable field to update the buckets for (e.g., title/creator)
115
+ * @inheritdoc
183
116
  */
184
- updatePrefixFilterCounts(filterType: PrefixFilterType): Promise<void>;
117
+ accountExtraInfo?: AccountExtraInfo;
185
118
 
186
119
  /**
187
- * Changes the page size used by the data source, discarding any previously-fetched pages.
188
- *
189
- * **Note: this operation will reset any data stored in the data source!**
190
- * @param pageSize
120
+ * @inheritdoc
191
121
  */
192
- setPageSize(pageSize: number): void;
122
+ pageElements?: PageElementMap;
193
123
 
194
124
  /**
195
- * Resets the data source to its empty state, with no result pages, aggregations, etc.
125
+ * @inheritdoc
196
126
  */
197
- reset(): void;
127
+ parentCollections?: string[] = [];
198
128
 
199
129
  /**
200
- * Notifies the data source that a query change has occurred, which may trigger a data
201
- * reset & new fetches.
130
+ * @inheritdoc
202
131
  */
203
- handleQueryChange(): void;
132
+ prefixFilterCountMap: Partial<Record<PrefixFilterType, PrefixFilterCounts>> =
133
+ {};
204
134
 
205
135
  /**
206
- * Applies the given map function to all of the tile models in every page of the data
207
- * source.
208
- * @param callback A callback function to apply on each tile model, as with Array.map
136
+ * @inheritdoc
209
137
  */
210
- map(
211
- callback: (model: TileModel, index: number, array: TileModel[]) => TileModel
212
- ): void;
138
+ queryErrorMessage?: string;
213
139
 
214
140
  /**
215
- * Checks every tile's management checkbox
141
+ * Internal property to store the `resolve` function for the most recent
142
+ * `initialSearchComplete` promise, allowing us to resolve it at the appropriate time.
216
143
  */
217
- checkAllTiles(): void;
144
+ private _initialSearchCompleteResolver!: (val: boolean) => void;
218
145
 
219
146
  /**
220
- * Unchecks every tile's management checkbox
147
+ * Internal property to store the private value backing the `initialSearchComplete` getter.
221
148
  */
222
- uncheckAllTiles(): void;
149
+ private _initialSearchCompletePromise: Promise<boolean> = new Promise(res => {
150
+ this._initialSearchCompleteResolver = res;
151
+ });
223
152
 
224
153
  /**
225
- * Removes all tile models that are currently checked & adjusts the paging
226
- * of the data source to account for any new gaps in the data.
154
+ * @inheritdoc
227
155
  */
228
- removeCheckedTiles(): void;
229
- }
230
-
231
- export class CollectionBrowserDataSource
232
- implements CollectionBrowserDataSourceInterface
233
- {
234
- private pages: Record<string, TileModel[]> = {};
156
+ get initialSearchComplete(): Promise<boolean> {
157
+ return this._initialSearchCompletePromise;
158
+ }
235
159
 
236
- private offset = 0;
160
+ // eslint-disable-next-line no-useless-constructor
161
+ constructor(
162
+ /** The host element to which this controller should attach listeners */
163
+ private readonly host: ReactiveControllerHost &
164
+ CollectionBrowserSearchInterface,
165
+ /** Default size of result pages */
166
+ private pageSize: number = 50
167
+ ) {
168
+ // Just setting some property values
169
+ }
237
170
 
238
- private numTileModels = 0;
171
+ hostConnected(): void {
172
+ this.setSearchResultsLoading(this.searchResultsLoading);
173
+ this.setFacetsLoading(this.facetsLoading);
174
+ }
239
175
 
240
- /**
241
- * Maps the full query key to the pages being fetched for that query
242
- */
243
- private pageFetchesInProgress: Record<string, Set<number>> = {};
176
+ hostUpdate(): void {
177
+ // This reactive controller hook is run whenever the host component (collection-browser) performs an update.
178
+ // We check whether the host's state has changed in a way which should trigger a reset & new results fetch.
244
179
 
245
- totalResults = 0;
180
+ // Only the currently-installed data source should react to the update
181
+ if (!this.activeOnHost) return;
246
182
 
247
- endOfDataReached = false;
183
+ // Copy loading states onto the host
184
+ this.setSearchResultsLoading(this.searchResultsLoading);
185
+ this.setFacetsLoading(this.facetsLoading);
248
186
 
249
- /**
250
- * @inheritdoc
251
- */
252
- aggregations?: Record<string, Aggregation>;
187
+ // Can't perform searches without a search service
188
+ if (!this.host.searchService) return;
253
189
 
254
- /**
255
- * @inheritdoc
256
- */
257
- yearHistogramAggregation?: Aggregation;
190
+ // We should only reset if part of the full query state has changed
191
+ const queryKeyChanged = this.pageFetchQueryKey !== this.previousQueryKey;
192
+ if (!queryKeyChanged) return;
258
193
 
259
- /**
260
- * @inheritdoc
261
- */
262
- collectionTitles = new Map<string, string>();
194
+ // We should only reset if either:
195
+ // (a) our state permits a valid search, or
196
+ // (b) we have a blank query that we want to show empty results for
197
+ const shouldShowEmptyQueryResults =
198
+ this.host.clearResultsOnEmptyQuery && this.host.baseQuery === '';
199
+ if (!(this.canPerformSearch || shouldShowEmptyQueryResults)) return;
263
200
 
264
- /**
265
- * @inheritdoc
266
- */
267
- collectionExtraInfo?: CollectionExtraInfo;
201
+ if (this.activeOnHost) this.host.emitQueryStateChanged();
202
+ this.handleQueryChange();
203
+ }
268
204
 
269
205
  /**
270
- * @inheritdoc
206
+ * Returns whether this data source is the one currently installed on the host component.
271
207
  */
272
- accountExtraInfo?: AccountExtraInfo;
208
+ private get activeOnHost(): boolean {
209
+ return this.host.dataSource === this;
210
+ }
273
211
 
274
212
  /**
275
213
  * @inheritdoc
276
214
  */
277
- pageElements?: PageElementMap;
215
+ get size(): number {
216
+ return this.numTileModels;
217
+ }
278
218
 
279
219
  /**
280
220
  * @inheritdoc
281
221
  */
282
- parentCollections?: string[] = [];
222
+ reset(): void {
223
+ log('Resetting CB data source');
224
+ this.pages = {};
225
+ this.aggregations = {};
226
+ this.yearHistogramAggregation = undefined;
227
+ this.pageElements = undefined;
228
+ this.parentCollections = [];
229
+ this.queryErrorMessage = undefined;
283
230
 
284
- /**
285
- * @inheritdoc
286
- */
287
- prefixFilterCountMap: Partial<Record<PrefixFilterType, PrefixFilterCounts>> =
288
- {};
231
+ this.offset = 0;
232
+ this.numTileModels = 0;
233
+ this.totalResults = 0;
234
+ this.endOfDataReached = false;
235
+ this.queryInitialized = false;
289
236
 
290
- constructor(
291
- /** The host element to which this controller should attach listeners */
292
- private readonly host: ReactiveControllerHost &
293
- CollectionBrowserSearchInterface,
294
- /** Default size of result pages */
295
- private pageSize: number
296
- ) {
297
- this.host.addController(this as CollectionBrowserDataSourceInterface);
298
- }
237
+ // Invalidate any fetches in progress
238
+ this.fetchesInProgress.clear();
299
239
 
300
- /**
301
- * @inheritdoc
302
- */
303
- get size(): number {
304
- return this.numTileModels;
240
+ if (this.activeOnHost) this.host.setTotalResultCount(0);
241
+ this.requestHostUpdate();
305
242
  }
306
243
 
307
244
  /**
@@ -310,7 +247,7 @@ export class CollectionBrowserDataSource
310
247
  addPage(pageNum: number, pageTiles: TileModel[]): void {
311
248
  this.pages[pageNum] = pageTiles;
312
249
  this.numTileModels += pageTiles.length;
313
- this.host.requestUpdate();
250
+ this.requestHostUpdate();
314
251
  }
315
252
 
316
253
  /**
@@ -350,6 +287,13 @@ export class CollectionBrowserDataSource
350
287
  return Object.values(this.pages).flat().indexOf(tile);
351
288
  }
352
289
 
290
+ /**
291
+ * @inheritdoc
292
+ */
293
+ getPageSize(): number {
294
+ return this.pageSize;
295
+ }
296
+
353
297
  /**
354
298
  * @inheritdoc
355
299
  */
@@ -361,32 +305,37 @@ export class CollectionBrowserDataSource
361
305
  /**
362
306
  * @inheritdoc
363
307
  */
364
- reset(): void {
365
- this.pages = {};
366
- this.aggregations = {};
367
- this.yearHistogramAggregation = undefined;
368
- this.pageFetchesInProgress = {};
369
- this.pageElements = undefined;
370
- this.parentCollections = [];
371
- this.prefixFilterCountMap = {};
372
-
373
- this.offset = 0;
374
- this.numTileModels = 0;
375
- this.totalResults = 0;
376
- this.endOfDataReached = false;
377
-
378
- this.host.requestUpdate();
308
+ setFetchesSuppressed(suppressed: boolean): void {
309
+ this.suppressFetches = suppressed;
379
310
  }
380
311
 
381
312
  /**
382
313
  * @inheritdoc
383
314
  */
384
315
  async handleQueryChange(): Promise<void> {
316
+ // Don't react to the change if fetches are suppressed for this data source
317
+ if (this.suppressFetches) return;
318
+
385
319
  this.reset();
320
+
321
+ // Reset the `initialSearchComplete` promise with a new value for the imminent search
322
+ this._initialSearchCompletePromise = new Promise(res => {
323
+ this._initialSearchCompleteResolver = res;
324
+ });
325
+
326
+ const shouldFetchFacets =
327
+ !this.host.suppressFacets &&
328
+ !FACETLESS_PAGE_ELEMENTS.includes(this.host.profileElement!);
329
+
330
+ // Fire the initial page & facet requests
331
+ this.queryInitialized = true;
386
332
  await Promise.all([
387
333
  this.doInitialPageFetch(),
388
- this.host.suppressFacets ? null : this.fetchFacets(),
334
+ shouldFetchFacets ? this.fetchFacets() : null,
389
335
  ]);
336
+
337
+ // Resolve the `initialSearchComplete` promise for this search
338
+ this._initialSearchCompleteResolver(true);
390
339
  }
391
340
 
392
341
  /**
@@ -403,22 +352,30 @@ export class CollectionBrowserDataSource
403
352
  ),
404
353
  ])
405
354
  );
406
- this.host.requestUpdate();
407
- this.host.refreshVisibleResults();
355
+ this.requestHostUpdate();
356
+ this.refreshVisibleResults();
408
357
  }
409
358
 
410
359
  /**
411
360
  * @inheritdoc
412
361
  */
413
362
  checkAllTiles = (): void => {
414
- this.map(model => ({ ...model, checked: true }));
363
+ this.map(model => {
364
+ const cloned = model.clone();
365
+ cloned.checked = true;
366
+ return cloned;
367
+ });
415
368
  };
416
369
 
417
370
  /**
418
371
  * @inheritdoc
419
372
  */
420
373
  uncheckAllTiles = (): void => {
421
- this.map(model => ({ ...model, checked: false }));
374
+ this.map(model => {
375
+ const cloned = model.clone();
376
+ cloned.checked = false;
377
+ return cloned;
378
+ });
422
379
  };
423
380
 
424
381
  /**
@@ -461,7 +418,8 @@ export class CollectionBrowserDataSource
461
418
  // Swap in the new pages
462
419
  this.pages = newPages;
463
420
  this.numTileModels -= numChecked;
464
- this.host.requestUpdate();
421
+ this.requestHostUpdate();
422
+ this.refreshVisibleResults();
465
423
  };
466
424
 
467
425
  /**
@@ -495,8 +453,6 @@ export class CollectionBrowserDataSource
495
453
  );
496
454
  }
497
455
 
498
- // DATA FETCHES
499
-
500
456
  /**
501
457
  * @inheritdoc
502
458
  */
@@ -519,6 +475,47 @@ export class CollectionBrowserDataSource
519
475
  );
520
476
  }
521
477
 
478
+ /**
479
+ * Sets the state for whether the initial set of search results for the
480
+ * current query is loading
481
+ */
482
+ private setSearchResultsLoading(loading: boolean): void {
483
+ this.searchResultsLoading = loading;
484
+ if (this.activeOnHost) {
485
+ this.host.setSearchResultsLoading(loading);
486
+ }
487
+ }
488
+
489
+ /**
490
+ * Sets the state for whether the facets for a query is loading
491
+ */
492
+ private setFacetsLoading(loading: boolean): void {
493
+ this.facetsLoading = loading;
494
+ if (this.activeOnHost) {
495
+ this.host.setFacetsLoading(loading);
496
+ }
497
+ }
498
+
499
+ /**
500
+ * Requests that the host perform an update, provided this data
501
+ * source is actively installed on it.
502
+ */
503
+ private requestHostUpdate(): void {
504
+ if (this.activeOnHost) {
505
+ this.host.requestUpdate();
506
+ }
507
+ }
508
+
509
+ /**
510
+ * Requests that the host refresh its visible tiles, provided this
511
+ * data source is actively installed on it.
512
+ */
513
+ private refreshVisibleResults(): void {
514
+ if (this.activeOnHost) {
515
+ this.host.refreshVisibleResults();
516
+ }
517
+ }
518
+
522
519
  /**
523
520
  * The query key is a string that uniquely identifies the current search.
524
521
  * It consists of:
@@ -530,15 +527,15 @@ export class CollectionBrowserDataSource
530
527
  * - Any currently-applied prefix filters
531
528
  * - The current sort options
532
529
  *
533
- * This lets us keep track of queries so we don't persist data that's
534
- * no longer relevant.
530
+ * This lets us internally keep track of queries so we don't persist data that's
531
+ * no longer relevant. Not meant to be human-readable.
535
532
  */
536
533
  get pageFetchQueryKey(): string {
537
- const profileKey = `${this.host.withinProfile}--${this.host.profileElement}`;
534
+ const profileKey = `pf;${this.host.withinProfile}--pe;${this.host.profileElement}`;
538
535
  const pageTarget = this.host.withinCollection ?? profileKey;
539
- const sortField = this.host.sortParam?.field ?? 'none';
540
- const sortDirection = this.host.sortParam?.direction ?? 'none';
541
- return `${this.fullQuery}-${pageTarget}-${this.host.searchType}-${sortField}-${sortDirection}`;
536
+ const sortField = this.host.selectedSort ?? 'none';
537
+ const sortDirection = this.host.sortDirection ?? 'none';
538
+ return `fq:${this.fullQuery}-pt:${pageTarget}-st:${this.host.searchType}-sf:${sortField}-sd:${sortDirection}`;
542
539
  }
543
540
 
544
541
  /**
@@ -546,9 +543,9 @@ export class CollectionBrowserDataSource
546
543
  * are not relevant in determining aggregation queries.
547
544
  */
548
545
  get facetFetchQueryKey(): string {
549
- const profileKey = `${this.host.withinProfile}--${this.host.profileElement}`;
546
+ const profileKey = `pf;${this.host.withinProfile}--pe;${this.host.profileElement}`;
550
547
  const pageTarget = this.host.withinCollection ?? profileKey;
551
- return `${this.fullQuery}-${pageTarget}-${this.host.searchType}`;
548
+ return `fq:${this.fullQuery}-pt:${pageTarget}-st:${this.host.searchType}`;
552
549
  }
553
550
 
554
551
  /**
@@ -818,6 +815,8 @@ export class CollectionBrowserDataSource
818
815
  if (!this.canPerformSearch) return;
819
816
 
820
817
  const { facetFetchQueryKey } = this;
818
+ if (this.fetchesInProgress.has(facetFetchQueryKey)) return;
819
+ this.fetchesInProgress.add(facetFetchQueryKey);
821
820
 
822
821
  const sortParams = this.host.sortParam ? [this.host.sortParam] : [];
823
822
  const params: SearchParams = {
@@ -835,7 +834,7 @@ export class CollectionBrowserDataSource
835
834
  'aggregations'
836
835
  );
837
836
 
838
- this.host.setFacetsLoading(true);
837
+ this.setFacetsLoading(true);
839
838
  const searchResponse = await this.host.searchService?.search(
840
839
  params,
841
840
  this.host.searchType
@@ -846,7 +845,8 @@ export class CollectionBrowserDataSource
846
845
  // If so, we just want to discard this set of aggregations because they are
847
846
  // likely no longer valid for the newer query.
848
847
  const queryChangedSinceFetch =
849
- facetFetchQueryKey !== this.facetFetchQueryKey;
848
+ !this.fetchesInProgress.has(facetFetchQueryKey);
849
+ this.fetchesInProgress.delete(facetFetchQueryKey);
850
850
  if (queryChangedSinceFetch) return;
851
851
 
852
852
  if (!success) {
@@ -861,6 +861,7 @@ export class CollectionBrowserDataSource
861
861
  );
862
862
  }
863
863
 
864
+ this.setFacetsLoading(false);
864
865
  return;
865
866
  }
866
867
 
@@ -876,18 +877,17 @@ export class CollectionBrowserDataSource
876
877
  this.yearHistogramAggregation =
877
878
  success?.response?.aggregations?.year_histogram;
878
879
 
879
- this.host.setFacetsLoading(false);
880
- this.host.requestUpdate();
880
+ this.setFacetsLoading(false);
881
+ this.requestHostUpdate();
881
882
  }
882
883
 
883
884
  /**
884
885
  * Performs the initial page fetch(es) for the current search state.
885
886
  */
886
887
  private async doInitialPageFetch(): Promise<void> {
887
- this.host.setSearchResultsLoading(true);
888
+ this.setSearchResultsLoading(true);
888
889
  // Try to batch 2 initial page requests when possible
889
890
  await this.fetchPage(this.host.initialPageNumber, 2);
890
- this.host.setSearchResultsLoading(false);
891
891
  }
892
892
 
893
893
  /**
@@ -899,19 +899,9 @@ export class CollectionBrowserDataSource
899
899
  * if `pageNumber != 1`, defaulting to a single page.
900
900
  */
901
901
  async fetchPage(pageNumber: number, numInitialPages = 1): Promise<void> {
902
- console.log(`fetchPage(${pageNumber})`, {
903
- canPerformSearch: this.canPerformSearch,
904
- baseQuery: this.host.baseQuery,
905
- });
906
902
  const trimmedQuery = this.host.baseQuery?.trim();
907
903
  if (!this.canPerformSearch) return;
908
904
 
909
- console.log(`fetchPage - canPerformSearch`, {
910
- hasPage: this.hasPage(pageNumber),
911
- endOfDataReached: this.endOfDataReached,
912
- pageFetchesInProgress: this.pageFetchesInProgress,
913
- selectedFacets: JSON.stringify(this.host.selectedFacets),
914
- });
915
905
  // if we already have data, don't fetch again
916
906
  if (this.hasPage(pageNumber)) return;
917
907
 
@@ -919,25 +909,36 @@ export class CollectionBrowserDataSource
919
909
 
920
910
  // Batch multiple initial page requests together if needed (e.g., can request
921
911
  // pages 1 and 2 together in a single request).
922
- const numPages = pageNumber === 1 ? numInitialPages : 1;
912
+ let numPages = pageNumber === 1 ? numInitialPages : 1;
923
913
  const numRows = this.pageSize * numPages;
924
914
 
925
915
  // if a fetch is already in progress for this query and page, don't fetch again
926
916
  const { pageFetchQueryKey } = this;
927
- const pageFetches =
928
- this.pageFetchesInProgress[pageFetchQueryKey] ?? new Set();
929
- if (pageFetches.has(pageNumber)) {
930
- console.log(
931
- `Skipping fetch for page ${pageNumber} because one is already in progress`
932
- );
933
- return;
934
- }
917
+ const currentPageKey = `${pageFetchQueryKey}-p:${pageNumber}`;
918
+ if (this.fetchesInProgress.has(currentPageKey)) return;
919
+
935
920
  for (let i = 0; i < numPages; i += 1) {
936
- pageFetches.add(pageNumber + i);
921
+ this.fetchesInProgress.add(`${pageFetchQueryKey}-p:${pageNumber + i}`);
922
+ }
923
+ this.previousQueryKey = pageFetchQueryKey;
924
+
925
+ let sortParams = this.host.sortParam ? [this.host.sortParam] : [];
926
+ // TODO eventually the PPS should handle these defaults natively
927
+ const isDefaultProfileSort =
928
+ this.host.withinProfile && this.host.selectedSort === SortField.default;
929
+ if (isDefaultProfileSort && this.host.defaultSortParam) {
930
+ const sortOption =
931
+ SORT_OPTIONS[this.host.defaultSortParam.field as SortField];
932
+ if (sortOption.searchServiceKey) {
933
+ sortParams = [
934
+ {
935
+ field: sortOption.searchServiceKey,
936
+ direction: 'desc',
937
+ },
938
+ ];
939
+ }
937
940
  }
938
- this.pageFetchesInProgress[pageFetchQueryKey] = pageFetches;
939
941
 
940
- const sortParams = this.host.sortParam ? [this.host.sortParam] : [];
941
942
  const params: SearchParams = {
942
943
  ...this.pageSpecifierParams,
943
944
  query: trimmedQuery || '',
@@ -949,50 +950,48 @@ export class CollectionBrowserDataSource
949
950
  };
950
951
  params.uid = await this.requestUID(params, 'hits');
951
952
 
952
- console.log('=== FIRING PAGE REQUEST ===');
953
+ log('=== FIRING PAGE REQUEST ===', params);
953
954
  const searchResponse = await this.host.searchService?.search(
954
955
  params,
955
956
  this.host.searchType
956
957
  );
957
- console.log('=== RECEIVED PAGE RESPONSE IN CB === ');
958
+ log('=== RECEIVED PAGE RESPONSE IN CB ===', searchResponse);
958
959
  const success = searchResponse?.success;
959
960
 
960
- // This is checking to see if the query has changed since the data was fetched.
961
- // If so, we just want to discard the data since there should be a new query
962
- // right behind it.
963
- const queryChangedSinceFetch = pageFetchQueryKey !== this.pageFetchQueryKey;
964
- if (queryChangedSinceFetch) return;
961
+ // This is checking to see if the fetch has been invalidated since it was fired off.
962
+ // If so, we just want to discard the response since it is for an obsolete query state.
963
+ if (!this.fetchesInProgress.has(currentPageKey)) return;
964
+ for (let i = 0; i < numPages; i += 1) {
965
+ this.fetchesInProgress.delete(`${pageFetchQueryKey}-p:${pageNumber + i}`);
966
+ }
965
967
 
966
968
  if (!success) {
967
969
  const errorMsg = searchResponse?.error?.message;
968
970
  const detailMsg = searchResponse?.error?.details?.message;
969
971
 
970
- this.host.queryErrorMessage = `${errorMsg ?? ''}${
972
+ this.queryErrorMessage = `${errorMsg ?? ''}${
971
973
  detailMsg ? `; ${detailMsg}` : ''
972
974
  }`;
973
975
 
974
- if (!this.host.queryErrorMessage) {
975
- this.host.queryErrorMessage =
976
- 'Missing or malformed response from backend';
976
+ if (!this.queryErrorMessage) {
977
+ this.queryErrorMessage = 'Missing or malformed response from backend';
977
978
  // @ts-ignore: Property 'Sentry' does not exist on type 'Window & typeof globalThis'
978
979
  window?.Sentry?.captureMessage?.(this.queryErrorMessage, 'error');
979
980
  }
980
981
 
981
- for (let i = 0; i < numPages; i += 1) {
982
- this.pageFetchesInProgress[pageFetchQueryKey]?.delete(pageNumber + i);
983
- }
984
-
985
- this.host.setSearchResultsLoading(false);
986
- this.host.requestUpdate();
982
+ this.setSearchResultsLoading(false);
983
+ this.requestHostUpdate();
987
984
  return;
988
985
  }
989
986
 
990
987
  this.totalResults = success.response.totalResults - this.offset;
991
- this.host.setTotalResultCount(this.totalResults);
988
+ if (this.activeOnHost) {
989
+ this.host.setTotalResultCount(this.totalResults);
992
990
 
993
- // display event to offshoot when result count is zero.
994
- if (this.totalResults === 0) {
995
- this.host.emitEmptyResults();
991
+ // display event to offshoot when result count is zero.
992
+ if (this.totalResults === 0) {
993
+ this.host.emitEmptyResults();
994
+ }
996
995
  }
997
996
 
998
997
  if (this.host.withinCollection) {
@@ -1000,7 +999,9 @@ export class CollectionBrowserDataSource
1000
999
 
1001
1000
  // For collections, we want the UI to respect the default sort option
1002
1001
  // which can be specified in metadata, or otherwise assumed to be week:desc
1003
- this.host.applyDefaultCollectionSort(this.collectionExtraInfo);
1002
+ if (this.activeOnHost) {
1003
+ this.host.applyDefaultCollectionSort(this.collectionExtraInfo);
1004
+ }
1004
1005
 
1005
1006
  if (this.collectionExtraInfo) {
1006
1007
  this.parentCollections = [].concat(
@@ -1008,10 +1009,6 @@ export class CollectionBrowserDataSource
1008
1009
  );
1009
1010
  }
1010
1011
  } else if (this.host.withinProfile) {
1011
- console.log(
1012
- 'host is within profile, setting acct info:',
1013
- success.response.accountExtraInfo
1014
- );
1015
1012
  this.accountExtraInfo = success.response.accountExtraInfo;
1016
1013
  this.pageElements = success.response.pageElements;
1017
1014
  }
@@ -1026,7 +1023,16 @@ export class CollectionBrowserDataSource
1026
1023
  }
1027
1024
  }
1028
1025
 
1029
- // Update the data source for each returned page
1026
+ // Update the data source for each returned page.
1027
+ // For loans and web archives, we must account for receiving more pages than we asked for.
1028
+ if (
1029
+ this.host.profileElement === 'lending' ||
1030
+ this.host.profileElement === 'web_archives'
1031
+ ) {
1032
+ numPages = Math.ceil(results.length / this.pageSize);
1033
+ this.endOfDataReached = true;
1034
+ if (this.activeOnHost) this.host.setTileCount(this.totalResults);
1035
+ }
1030
1036
  for (let i = 0; i < numPages; i += 1) {
1031
1037
  const pageStartIndex = this.pageSize * i;
1032
1038
  this.addTilesToDataSource(
@@ -1042,14 +1048,11 @@ export class CollectionBrowserDataSource
1042
1048
  const resultCountDiscrepancy = numRows - results.length;
1043
1049
  if (resultCountDiscrepancy > 0) {
1044
1050
  this.endOfDataReached = true;
1045
- this.host.setTileCount(this.totalResults);
1046
- }
1047
-
1048
- for (let i = 0; i < numPages; i += 1) {
1049
- this.pageFetchesInProgress[pageFetchQueryKey]?.delete(pageNumber + i);
1051
+ if (this.activeOnHost) this.host.setTileCount(this.totalResults);
1050
1052
  }
1051
1053
 
1052
- this.host.requestUpdate();
1054
+ this.setSearchResultsLoading(false);
1055
+ this.requestHostUpdate();
1053
1056
  }
1054
1057
 
1055
1058
  /**
@@ -1062,110 +1065,19 @@ export class CollectionBrowserDataSource
1062
1065
  pageNumber: number,
1063
1066
  results: SearchResult[]
1064
1067
  ): void {
1065
- // copy our existing datasource so when we set it below, it gets set
1066
- // instead of modifying the existing dataSource since object changes
1067
- // don't trigger a re-render
1068
- // const datasource = { ...this.dataSource };
1069
1068
  const tiles: TileModel[] = [];
1070
1069
  results?.forEach(result => {
1071
1070
  if (!result.identifier) return;
1072
-
1073
- let loginRequired = false;
1074
- let contentWarning = false;
1075
- // Check if item and item in "modifying" collection, setting above flags
1076
- if (
1077
- result.collection?.values.length &&
1078
- result.mediatype?.value !== 'collection'
1079
- ) {
1080
- for (const collection of result.collection?.values ?? []) {
1081
- if (collection === 'loggedin') {
1082
- loginRequired = true;
1083
- if (contentWarning) break;
1084
- }
1085
- if (collection === 'no-preview') {
1086
- contentWarning = true;
1087
- if (loginRequired) break;
1088
- }
1089
- }
1090
- }
1091
-
1092
- tiles.push({
1093
- averageRating: result.avg_rating?.value,
1094
- checked: false,
1095
- collections: result.collection?.values ?? [],
1096
- collectionFilesCount: result.collection_files_count?.value ?? 0,
1097
- collectionSize: result.collection_size?.value ?? 0,
1098
- commentCount: result.num_reviews?.value ?? 0,
1099
- creator: result.creator?.value,
1100
- creators: result.creator?.values ?? [],
1101
- dateAdded: result.addeddate?.value,
1102
- dateArchived: result.publicdate?.value,
1103
- datePublished: result.date?.value,
1104
- dateReviewed: result.reviewdate?.value,
1105
- description: result.description?.values.join('\n'),
1106
- favCount: result.num_favorites?.value ?? 0,
1107
- href: this.collapseRepeatedQuotes(result.__href__?.value),
1108
- identifier: result.identifier,
1109
- issue: result.issue?.value,
1110
- itemCount: result.item_count?.value ?? 0,
1111
- mediatype: this.getMediatype(result),
1112
- snippets: result.highlight?.values ?? [],
1113
- source: result.source?.value,
1114
- subjects: result.subject?.values ?? [],
1115
- title: result.title?.value ?? '',
1116
- volume: result.volume?.value,
1117
- viewCount: result.downloads?.value ?? 0,
1118
- weeklyViewCount: result.week?.value,
1119
- loginRequired,
1120
- contentWarning,
1121
- });
1071
+ tiles.push(new TileModel(result));
1122
1072
  });
1123
1073
  this.addPage(pageNumber, tiles);
1124
1074
  const visiblePages = this.host.currentVisiblePageNumbers;
1125
1075
  const needsReload = visiblePages.includes(pageNumber);
1126
1076
  if (needsReload) {
1127
- this.host.refreshVisibleResults();
1077
+ this.refreshVisibleResults();
1128
1078
  }
1129
1079
  }
1130
1080
 
1131
- /**
1132
- * Returns the mediatype string for the given search result, taking into account
1133
- * the special `favorited_search` hit type.
1134
- * @param result The search result to extract a mediatype from
1135
- */
1136
- private getMediatype(result: SearchResult): MediaType {
1137
- /**
1138
- * hit_type == 'favorited_search' is basically a new hit_type
1139
- * - we are getting from PPS.
1140
- * - which gives response for fav- collection
1141
- * - having favorited items like account/collection/item etc..
1142
- * - as user can also favorite a search result (a search page)
1143
- * - so we need to have response (having fav- items and fav- search results)
1144
- *
1145
- * if backend hit_type == 'favorited_search'
1146
- * - let's assume a "search" as new mediatype
1147
- */
1148
- if (result?.rawMetadata?.hit_type === 'favorited_search') {
1149
- return 'search';
1150
- }
1151
-
1152
- return result.mediatype?.value ?? 'data';
1153
- }
1154
-
1155
- /**
1156
- * Returns the input string, but removing one set of quotes from all instances of
1157
- * ""clauses wrapped in two sets of quotes"". This assumes the quotes are already
1158
- * URL-encoded.
1159
- *
1160
- * This should be a temporary measure to address the fact that the __href__ field
1161
- * sometimes acquires extra quotation marks during query rewriting. Once there is a
1162
- * full Lucene parser in place that handles quoted queries correctly, this can likely
1163
- * be removed.
1164
- */
1165
- private collapseRepeatedQuotes(str?: string): string | undefined {
1166
- return str?.replace(/%22%22(?!%22%22)(.+?)%22%22/g, '%22$1%22');
1167
- }
1168
-
1169
1081
  /**
1170
1082
  * Fetches the aggregation buckets for the given prefix filter type.
1171
1083
  */
@@ -1226,6 +1138,29 @@ export class CollectionBrowserDataSource
1226
1138
  {}
1227
1139
  );
1228
1140
 
1229
- this.host.requestUpdate();
1141
+ this.requestHostUpdate();
1142
+ }
1143
+
1144
+ /**
1145
+ * @inheritdoc
1146
+ */
1147
+ async updatePrefixFiltersForCurrentSort(): Promise<void> {
1148
+ if (['title', 'creator'].includes(this.host.selectedSort as SortField)) {
1149
+ const filterType = this.host.selectedSort as PrefixFilterType;
1150
+ if (!this.prefixFilterCountMap[filterType]) {
1151
+ this.updatePrefixFilterCounts(filterType);
1152
+ }
1153
+ }
1154
+ }
1155
+
1156
+ /**
1157
+ * @inheritdoc
1158
+ */
1159
+ refreshLetterCounts(): void {
1160
+ if (Object.keys(this.prefixFilterCountMap).length > 0) {
1161
+ this.prefixFilterCountMap = {};
1162
+ }
1163
+ this.updatePrefixFiltersForCurrentSort();
1164
+ this.requestHostUpdate();
1230
1165
  }
1231
1166
  }