@internetarchive/collection-browser 0.4.16-alpha.9 → 0.4.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/app-root.js +12 -0
- package/dist/src/app-root.js.map +1 -1
- package/dist/src/collection-browser.d.ts +53 -14
- package/dist/src/collection-browser.js +279 -113
- package/dist/src/collection-browser.js.map +1 -1
- package/dist/src/collection-facets/facets-template.d.ts +3 -0
- package/dist/src/collection-facets/facets-template.js +20 -1
- package/dist/src/collection-facets/facets-template.js.map +1 -1
- package/dist/src/collection-facets/more-facets-content.js +7 -4
- package/dist/src/collection-facets/more-facets-content.js.map +1 -1
- package/dist/src/collection-facets.d.ts +0 -2
- package/dist/src/collection-facets.js +1 -4
- package/dist/src/collection-facets.js.map +1 -1
- package/dist/src/empty-placeholder.js +1 -0
- package/dist/src/empty-placeholder.js.map +1 -1
- package/dist/src/models.d.ts +5 -0
- package/dist/src/models.js.map +1 -1
- package/dist/src/tiles/grid/item-tile.js +10 -3
- package/dist/src/tiles/grid/item-tile.js.map +1 -1
- package/dist/src/tiles/list/tile-list-compact.d.ts +1 -0
- package/dist/src/tiles/list/tile-list-compact.js +13 -1
- package/dist/src/tiles/list/tile-list-compact.js.map +1 -1
- package/dist/src/tiles/list/tile-list.js +10 -1
- package/dist/src/tiles/list/tile-list.js.map +1 -1
- package/dist/src/utils/format-date.d.ts +1 -1
- package/dist/src/utils/format-date.js +3 -0
- package/dist/src/utils/format-date.js.map +1 -1
- package/dist/src/utils/local-date-from-utc.d.ts +9 -0
- package/dist/src/utils/local-date-from-utc.js +16 -0
- package/dist/src/utils/local-date-from-utc.js.map +1 -0
- package/dist/test/collection-browser.test.js +73 -10
- package/dist/test/collection-browser.test.js.map +1 -1
- package/dist/test/collection-facets/facets-template.test.js +80 -0
- package/dist/test/collection-facets/facets-template.test.js.map +1 -1
- package/dist/test/mocks/mock-collection-name-cache.d.ts +2 -0
- package/dist/test/mocks/mock-collection-name-cache.js +4 -0
- package/dist/test/mocks/mock-collection-name-cache.js.map +1 -1
- package/dist/test/mocks/mock-search-responses.d.ts +2 -0
- package/dist/test/mocks/mock-search-responses.js +81 -0
- package/dist/test/mocks/mock-search-responses.js.map +1 -1
- package/dist/test/mocks/mock-search-service.js +3 -1
- package/dist/test/mocks/mock-search-service.js.map +1 -1
- package/dist/test/tiles/grid/item-tile.test.js +124 -3
- package/dist/test/tiles/grid/item-tile.test.js.map +1 -1
- package/dist/test/tiles/list/tile-list-compact.test.js +65 -20
- package/dist/test/tiles/list/tile-list-compact.test.js.map +1 -1
- package/dist/test/tiles/list/tile-list.test.js +106 -4
- package/dist/test/tiles/list/tile-list.test.js.map +1 -1
- package/dist/test/utils/local-date-from-utc.test.d.ts +1 -0
- package/dist/test/utils/local-date-from-utc.test.js +27 -0
- package/dist/test/utils/local-date-from-utc.test.js.map +1 -0
- package/index.html +1 -0
- package/package.json +3 -3
- package/src/app-root.ts +12 -0
- package/src/collection-browser.ts +311 -114
- package/src/collection-facets/facets-template.ts +32 -1
- package/src/collection-facets/more-facets-content.ts +4 -1
- package/src/collection-facets.ts +1 -8
- package/src/empty-placeholder.ts +1 -0
- package/src/models.ts +6 -0
- package/src/tiles/grid/item-tile.ts +11 -4
- package/src/tiles/list/tile-list-compact.ts +16 -2
- package/src/tiles/list/tile-list.ts +12 -5
- package/src/utils/format-date.ts +4 -0
- package/src/utils/local-date-from-utc.ts +15 -0
- package/test/collection-browser.test.ts +101 -12
- package/test/collection-facets/facets-template.test.ts +98 -0
- package/test/mocks/mock-collection-name-cache.ts +8 -0
- package/test/mocks/mock-search-responses.ts +89 -0
- package/test/mocks/mock-search-service.ts +4 -0
- package/test/tiles/grid/item-tile.test.ts +145 -3
- package/test/tiles/list/tile-list-compact.test.ts +70 -19
- package/test/tiles/list/tile-list.test.ts +118 -4
- package/test/utils/local-date-from-utc.test.ts +37 -0
|
@@ -55,7 +55,7 @@ import {
|
|
|
55
55
|
PrefixFilterType,
|
|
56
56
|
PrefixFilterCounts,
|
|
57
57
|
prefixFilterAggregationKeys,
|
|
58
|
-
|
|
58
|
+
FacetEventDetails,
|
|
59
59
|
} from './models';
|
|
60
60
|
import {
|
|
61
61
|
RestorationStateHandlerInterface,
|
|
@@ -104,10 +104,6 @@ export class CollectionBrowser
|
|
|
104
104
|
|
|
105
105
|
@property({ type: Object }) resizeObserver?: SharedResizeObserverInterface;
|
|
106
106
|
|
|
107
|
-
@property({ type: String }) titleQuery?: string;
|
|
108
|
-
|
|
109
|
-
@property({ type: String }) creatorQuery?: string;
|
|
110
|
-
|
|
111
107
|
@property({ type: Number }) currentPage?: number;
|
|
112
108
|
|
|
113
109
|
@property({ type: String }) minSelectedDate?: string;
|
|
@@ -189,6 +185,8 @@ export class CollectionBrowser
|
|
|
189
185
|
|
|
190
186
|
@query('#content-container') private contentContainer!: HTMLDivElement;
|
|
191
187
|
|
|
188
|
+
@query('#left-column') private leftColumn?: HTMLDivElement;
|
|
189
|
+
|
|
192
190
|
@property({ type: Object, attribute: false })
|
|
193
191
|
private analyticsHandler?: AnalyticsManagerInterface;
|
|
194
192
|
|
|
@@ -209,6 +207,10 @@ export class CollectionBrowser
|
|
|
209
207
|
*/
|
|
210
208
|
private isResizeToMobile = false;
|
|
211
209
|
|
|
210
|
+
private leftColIntersectionObserver?: IntersectionObserver;
|
|
211
|
+
|
|
212
|
+
private facetsIntersectionObserver?: IntersectionObserver;
|
|
213
|
+
|
|
212
214
|
private placeholderCellTemplate = html`<collection-browser-loading-tile></collection-browser-loading-tile>`;
|
|
213
215
|
|
|
214
216
|
private tileModelAtCellIndex(index: number): TileModel | undefined {
|
|
@@ -294,8 +296,6 @@ export class CollectionBrowser
|
|
|
294
296
|
if (letterFilters) {
|
|
295
297
|
this.selectedTitleFilter = null;
|
|
296
298
|
this.selectedCreatorFilter = null;
|
|
297
|
-
this.titleQuery = undefined;
|
|
298
|
-
this.creatorQuery = undefined;
|
|
299
299
|
}
|
|
300
300
|
|
|
301
301
|
if (sort) {
|
|
@@ -339,11 +339,9 @@ export class CollectionBrowser
|
|
|
339
339
|
this.placeholderType = null;
|
|
340
340
|
if (!this.baseQuery?.trim()) {
|
|
341
341
|
this.placeholderType = 'empty-query';
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
(!this.searchResultsLoading && this.totalResults === 0) ||
|
|
346
|
-
!this.searchService
|
|
342
|
+
} else if (
|
|
343
|
+
!this.searchResultsLoading &&
|
|
344
|
+
(this.totalResults === 0 || !this.searchService)
|
|
347
345
|
) {
|
|
348
346
|
this.placeholderType = 'null-result';
|
|
349
347
|
}
|
|
@@ -370,7 +368,8 @@ export class CollectionBrowser
|
|
|
370
368
|
this.searchResultsLoading || this.totalResults === undefined;
|
|
371
369
|
const resultsCount = this.totalResults?.toLocaleString();
|
|
372
370
|
const resultsLabel = this.totalResults === 1 ? 'Result' : 'Results';
|
|
373
|
-
return html
|
|
371
|
+
return html` <div id="left-column-scroll-sentinel"></div>
|
|
372
|
+
<div
|
|
374
373
|
id="left-column"
|
|
375
374
|
class="column${this.isResizeToMobile ? ' preload' : ''}"
|
|
376
375
|
>
|
|
@@ -392,7 +391,9 @@ export class CollectionBrowser
|
|
|
392
391
|
: ''}
|
|
393
392
|
>
|
|
394
393
|
${this.facetsTemplate}
|
|
394
|
+
<div id="facets-scroll-sentinel"></div>
|
|
395
395
|
</div>
|
|
396
|
+
${this.mobileView ? nothing : html`<div id="facets-bottom-fade"></div>`}
|
|
396
397
|
</div>
|
|
397
398
|
<div id="right-column" class="column">
|
|
398
399
|
${this.sortFilterBarTemplate}
|
|
@@ -497,9 +498,30 @@ export class CollectionBrowser
|
|
|
497
498
|
}
|
|
498
499
|
}
|
|
499
500
|
|
|
500
|
-
/**
|
|
501
|
+
/**
|
|
502
|
+
* Returns a query clause identifying the currently selected title filter,
|
|
503
|
+
* e.g., `firstTitle:X`.
|
|
504
|
+
*/
|
|
505
|
+
private get titleQuery(): string | undefined {
|
|
506
|
+
return this.selectedTitleFilter
|
|
507
|
+
? `firstTitle:${this.selectedTitleFilter}`
|
|
508
|
+
: undefined;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* Returns a query clause identifying the currently selected creator filter,
|
|
513
|
+
* e.g., `firstCreator:X`.
|
|
514
|
+
*/
|
|
515
|
+
private get creatorQuery(): string | undefined {
|
|
516
|
+
return this.selectedCreatorFilter
|
|
517
|
+
? `firstCreator:${this.selectedCreatorFilter}`
|
|
518
|
+
: undefined;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* Send Analytics when sorting by title's first letter
|
|
501
523
|
* labels: 'start-<ToLetter>' | 'clear-<FromLetter>' | '<FromLetter>-<ToLetter>'
|
|
502
|
-
|
|
524
|
+
*/
|
|
503
525
|
private sendFilterByTitleAnalytics(prevSelectedLetter: string | null): void {
|
|
504
526
|
if (!prevSelectedLetter && !this.selectedTitleFilter) {
|
|
505
527
|
return;
|
|
@@ -515,15 +537,10 @@ export class CollectionBrowser
|
|
|
515
537
|
});
|
|
516
538
|
}
|
|
517
539
|
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
? `firstTitle:${this.selectedTitleFilter}`
|
|
521
|
-
: undefined;
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
/** Send Analytics when filtering by creator's first letter
|
|
540
|
+
/**
|
|
541
|
+
* Send Analytics when filtering by creator's first letter
|
|
525
542
|
* labels: 'start-<ToLetter>' | 'clear-<FromLetter>' | '<FromLetter>-<ToLetter>'
|
|
526
|
-
|
|
543
|
+
*/
|
|
527
544
|
private sendFilterByCreatorAnalytics(
|
|
528
545
|
prevSelectedLetter: string | null
|
|
529
546
|
): void {
|
|
@@ -541,26 +558,24 @@ export class CollectionBrowser
|
|
|
541
558
|
});
|
|
542
559
|
}
|
|
543
560
|
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
: undefined;
|
|
548
|
-
}
|
|
549
|
-
|
|
561
|
+
/**
|
|
562
|
+
* Handler for changes to which letter is selected in the title alphabet bar.
|
|
563
|
+
*/
|
|
550
564
|
private titleLetterSelected(
|
|
551
565
|
e: CustomEvent<{ selectedLetter: string | null }>
|
|
552
566
|
): void {
|
|
553
567
|
this.selectedCreatorFilter = null;
|
|
554
568
|
this.selectedTitleFilter = e.detail.selectedLetter;
|
|
555
|
-
this.selectedTitleLetterChanged();
|
|
556
569
|
}
|
|
557
570
|
|
|
571
|
+
/**
|
|
572
|
+
* Handler for changes to which letter is selected in the creator alphabet bar.
|
|
573
|
+
*/
|
|
558
574
|
private creatorLetterSelected(
|
|
559
575
|
e: CustomEvent<{ selectedLetter: string | null }>
|
|
560
576
|
): void {
|
|
561
577
|
this.selectedTitleFilter = null;
|
|
562
578
|
this.selectedCreatorFilter = e.detail.selectedLetter;
|
|
563
|
-
this.selectedCreatorLetterChanged();
|
|
564
579
|
}
|
|
565
580
|
|
|
566
581
|
private get mobileFacetsTemplate() {
|
|
@@ -603,13 +618,13 @@ export class CollectionBrowser
|
|
|
603
618
|
.selectedFacets=${this.selectedFacets}
|
|
604
619
|
.collectionNameCache=${this.collectionNameCache}
|
|
605
620
|
.showHistogramDatePicker=${this.showHistogramDatePicker}
|
|
606
|
-
.query=${this.
|
|
621
|
+
.query=${this.baseQuery}
|
|
607
622
|
.filterMap=${this.filterMap}
|
|
608
623
|
.modalManager=${this.modalManager}
|
|
609
624
|
?collapsableFacets=${this.mobileView}
|
|
610
625
|
?facetsLoading=${this.facetsLoading}
|
|
611
626
|
?fullYearAggregationLoading=${this.facetsLoading}
|
|
612
|
-
|
|
627
|
+
@facetClick=${this.facetClickHandler}
|
|
613
628
|
.analyticsHandler=${this.analyticsHandler}
|
|
614
629
|
>
|
|
615
630
|
</collection-facets>
|
|
@@ -669,6 +684,16 @@ export class CollectionBrowser
|
|
|
669
684
|
}
|
|
670
685
|
|
|
671
686
|
updated(changed: PropertyValues) {
|
|
687
|
+
if (changed.has('placeholderType') && this.placeholderType === null) {
|
|
688
|
+
if (!this.leftColIntersectionObserver) {
|
|
689
|
+
this.setupLeftColumnScrollListeners();
|
|
690
|
+
}
|
|
691
|
+
if (!this.facetsIntersectionObserver) {
|
|
692
|
+
this.setupFacetsScrollListeners();
|
|
693
|
+
}
|
|
694
|
+
this.updateLeftColumnHeight();
|
|
695
|
+
}
|
|
696
|
+
|
|
672
697
|
if (
|
|
673
698
|
changed.has('displayMode') ||
|
|
674
699
|
changed.has('baseNavigationUrl') ||
|
|
@@ -729,13 +754,11 @@ export class CollectionBrowser
|
|
|
729
754
|
this.sendFilterByTitleAnalytics(
|
|
730
755
|
changed.get('selectedTitleFilter') as string
|
|
731
756
|
);
|
|
732
|
-
this.selectedTitleLetterChanged();
|
|
733
757
|
}
|
|
734
758
|
if (changed.has('selectedCreatorFilter')) {
|
|
735
759
|
this.sendFilterByCreatorAnalytics(
|
|
736
760
|
changed.get('selectedCreatorFilter') as string
|
|
737
761
|
);
|
|
738
|
-
this.selectedCreatorLetterChanged();
|
|
739
762
|
}
|
|
740
763
|
|
|
741
764
|
if (
|
|
@@ -774,6 +797,10 @@ export class CollectionBrowser
|
|
|
774
797
|
if (this.boundNavigationHandler) {
|
|
775
798
|
window.removeEventListener('popstate', this.boundNavigationHandler);
|
|
776
799
|
}
|
|
800
|
+
|
|
801
|
+
this.leftColIntersectionObserver?.disconnect();
|
|
802
|
+
this.facetsIntersectionObserver?.disconnect();
|
|
803
|
+
window.removeEventListener('resize', this.updateLeftColumnHeight);
|
|
777
804
|
}
|
|
778
805
|
|
|
779
806
|
handleResize(entry: ResizeObserverEntry): void {
|
|
@@ -785,8 +812,80 @@ export class CollectionBrowser
|
|
|
785
812
|
this.isResizeToMobile = true;
|
|
786
813
|
}
|
|
787
814
|
}
|
|
815
|
+
|
|
816
|
+
// Ensure the facet sidebar remains sized correctly
|
|
817
|
+
this.updateLeftColumnHeight();
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
/**
|
|
821
|
+
* Sets up listeners for events that may require updating the left column height.
|
|
822
|
+
*/
|
|
823
|
+
private setupLeftColumnScrollListeners(): void {
|
|
824
|
+
// We observe intersections between the left column's scroll sentinel and
|
|
825
|
+
// the viewport, so that we can ensure the left column is always sized to
|
|
826
|
+
// match the _available_ viewport height. This should generally be more
|
|
827
|
+
// performant than listening to scroll events on the page or column.
|
|
828
|
+
const leftColumnSentinel = this.shadowRoot?.querySelector(
|
|
829
|
+
'#left-column-scroll-sentinel'
|
|
830
|
+
);
|
|
831
|
+
if (leftColumnSentinel) {
|
|
832
|
+
this.leftColIntersectionObserver = new IntersectionObserver(
|
|
833
|
+
this.updateLeftColumnHeight,
|
|
834
|
+
{
|
|
835
|
+
threshold: [...Array(101).keys()].map(n => n / 100), // Threshold every 1%
|
|
836
|
+
}
|
|
837
|
+
);
|
|
838
|
+
this.leftColIntersectionObserver.observe(leftColumnSentinel);
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
// We also listen for window resize events, as they are not always captured
|
|
842
|
+
// by the resize observer and can affect the desired height of the left column.
|
|
843
|
+
window.addEventListener('resize', this.updateLeftColumnHeight);
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
/**
|
|
847
|
+
* Sets up listeners to control whether the facet sidebar shows its bottom fade-out.
|
|
848
|
+
* Note this uses a separate IntersectionObserver from the left column, because we
|
|
849
|
+
* don't need granular intersection thresholds for this.
|
|
850
|
+
*/
|
|
851
|
+
private setupFacetsScrollListeners(): void {
|
|
852
|
+
const facetsSentinel = this.shadowRoot?.querySelector(
|
|
853
|
+
'#facets-scroll-sentinel'
|
|
854
|
+
);
|
|
855
|
+
if (facetsSentinel) {
|
|
856
|
+
this.facetsIntersectionObserver = new IntersectionObserver(
|
|
857
|
+
this.updateFacetFadeOut
|
|
858
|
+
);
|
|
859
|
+
this.facetsIntersectionObserver.observe(facetsSentinel);
|
|
860
|
+
}
|
|
788
861
|
}
|
|
789
862
|
|
|
863
|
+
/**
|
|
864
|
+
* Updates the height of the left column according to its position on the page.
|
|
865
|
+
* Arrow function ensures proper `this` binding.
|
|
866
|
+
*/
|
|
867
|
+
private updateLeftColumnHeight = (): void => {
|
|
868
|
+
if (this.mobileView) {
|
|
869
|
+
this.leftColumn?.style?.removeProperty('height');
|
|
870
|
+
} else {
|
|
871
|
+
const clientTop = this.leftColumn?.getBoundingClientRect().top;
|
|
872
|
+
this.leftColumn?.style?.setProperty(
|
|
873
|
+
'height',
|
|
874
|
+
`${window.innerHeight - (clientTop ?? 0) - 3}px`
|
|
875
|
+
);
|
|
876
|
+
}
|
|
877
|
+
};
|
|
878
|
+
|
|
879
|
+
/**
|
|
880
|
+
* Toggles whether the fade-out is visible at the bottom of the facets.
|
|
881
|
+
* It should only be visible if the facets are not scrolled to the bottom.
|
|
882
|
+
* Arrow function ensures proper `this` binding.
|
|
883
|
+
*/
|
|
884
|
+
private updateFacetFadeOut = (entries: IntersectionObserverEntry[]): void => {
|
|
885
|
+
const fadeElmt = this.shadowRoot?.getElementById('facets-bottom-fade');
|
|
886
|
+
fadeElmt?.classList.toggle('hidden', entries?.[0]?.isIntersecting);
|
|
887
|
+
};
|
|
888
|
+
|
|
790
889
|
private emitBaseQueryChanged() {
|
|
791
890
|
this.dispatchEvent(
|
|
792
891
|
new CustomEvent<{ baseQuery?: string }>('baseQueryChanged', {
|
|
@@ -952,6 +1051,11 @@ export class CollectionBrowser
|
|
|
952
1051
|
this.searchResultsLoading = false;
|
|
953
1052
|
}
|
|
954
1053
|
|
|
1054
|
+
/**
|
|
1055
|
+
* Constructs a search service FilterMap object from the combination of
|
|
1056
|
+
* all the currently-applied filters. This includes any facets, letter
|
|
1057
|
+
* filters, and date range.
|
|
1058
|
+
*/
|
|
955
1059
|
private get filterMap(): FilterMap {
|
|
956
1060
|
const builder = new FilterMapBuilder();
|
|
957
1061
|
|
|
@@ -995,21 +1099,24 @@ export class CollectionBrowser
|
|
|
995
1099
|
}
|
|
996
1100
|
}
|
|
997
1101
|
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1102
|
+
// Add any letter filters
|
|
1103
|
+
if (this.selectedTitleFilter) {
|
|
1104
|
+
builder.addFilter(
|
|
1105
|
+
'firstTitle',
|
|
1106
|
+
this.selectedTitleFilter,
|
|
1107
|
+
FilterConstraint.INCLUDE
|
|
1108
|
+
);
|
|
1109
|
+
}
|
|
1110
|
+
if (this.selectedCreatorFilter) {
|
|
1111
|
+
builder.addFilter(
|
|
1112
|
+
'firstCreator',
|
|
1113
|
+
this.selectedCreatorFilter,
|
|
1114
|
+
FilterConstraint.INCLUDE
|
|
1115
|
+
);
|
|
1010
1116
|
}
|
|
1011
1117
|
|
|
1012
|
-
|
|
1118
|
+
const filterMap = builder.build();
|
|
1119
|
+
return filterMap;
|
|
1013
1120
|
}
|
|
1014
1121
|
|
|
1015
1122
|
/** The full query, including year facets and date range clauses */
|
|
@@ -1031,22 +1138,6 @@ export class CollectionBrowser
|
|
|
1031
1138
|
return fullQuery.trim();
|
|
1032
1139
|
}
|
|
1033
1140
|
|
|
1034
|
-
/** The full query without any title/creator letter filters */
|
|
1035
|
-
private get fullQueryWithoutAlphaFilters(): string | undefined {
|
|
1036
|
-
if (!this.baseQuery) return undefined;
|
|
1037
|
-
let fullQuery = this.baseQuery.trim();
|
|
1038
|
-
|
|
1039
|
-
const { facetQuery, dateRangeQueryClause } = this;
|
|
1040
|
-
|
|
1041
|
-
if (facetQuery) {
|
|
1042
|
-
fullQuery += ` AND ${facetQuery}`;
|
|
1043
|
-
}
|
|
1044
|
-
if (dateRangeQueryClause) {
|
|
1045
|
-
fullQuery += ` AND ${dateRangeQueryClause}`;
|
|
1046
|
-
}
|
|
1047
|
-
return fullQuery.trim();
|
|
1048
|
-
}
|
|
1049
|
-
|
|
1050
1141
|
/**
|
|
1051
1142
|
* Generates a query string for the given facets
|
|
1052
1143
|
*
|
|
@@ -1137,36 +1228,39 @@ export class CollectionBrowser
|
|
|
1137
1228
|
this.selectedFacets = e.detail;
|
|
1138
1229
|
}
|
|
1139
1230
|
|
|
1140
|
-
facetClickHandler(
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
negative: boolean
|
|
1144
|
-
): void {
|
|
1231
|
+
facetClickHandler({
|
|
1232
|
+
detail: { key, state: facetState, negative },
|
|
1233
|
+
}: CustomEvent<FacetEventDetails>): void {
|
|
1145
1234
|
if (negative) {
|
|
1146
1235
|
this.analyticsHandler?.sendEvent({
|
|
1147
1236
|
category: this.searchContext,
|
|
1148
|
-
action:
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1237
|
+
action:
|
|
1238
|
+
facetState !== 'none'
|
|
1239
|
+
? analyticsActions.facetNegativeSelected
|
|
1240
|
+
: analyticsActions.facetNegativeDeselected,
|
|
1241
|
+
label: key,
|
|
1152
1242
|
});
|
|
1153
1243
|
} else {
|
|
1154
1244
|
this.analyticsHandler?.sendEvent({
|
|
1155
1245
|
category: this.searchContext,
|
|
1156
|
-
action:
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1246
|
+
action:
|
|
1247
|
+
facetState !== 'none'
|
|
1248
|
+
? analyticsActions.facetSelected
|
|
1249
|
+
: analyticsActions.facetDeselected,
|
|
1250
|
+
label: key,
|
|
1160
1251
|
});
|
|
1161
1252
|
}
|
|
1162
1253
|
}
|
|
1163
1254
|
|
|
1164
1255
|
private async fetchFacets() {
|
|
1165
|
-
|
|
1256
|
+
const trimmedQuery = this.baseQuery?.trim();
|
|
1257
|
+
if (!trimmedQuery) return;
|
|
1166
1258
|
if (!this.searchService) return;
|
|
1167
1259
|
|
|
1260
|
+
const { facetFetchQueryKey } = this;
|
|
1261
|
+
|
|
1168
1262
|
const params: SearchParams = {
|
|
1169
|
-
query:
|
|
1263
|
+
query: trimmedQuery,
|
|
1170
1264
|
rows: 0,
|
|
1171
1265
|
filters: this.filterMap,
|
|
1172
1266
|
// Fetch a few extra buckets beyond the 6 we show, in case some get suppressed
|
|
@@ -1182,7 +1276,13 @@ export class CollectionBrowser
|
|
|
1182
1276
|
this.searchType
|
|
1183
1277
|
);
|
|
1184
1278
|
const success = searchResponse?.success;
|
|
1185
|
-
|
|
1279
|
+
|
|
1280
|
+
// This is checking to see if the query has changed since the data was fetched.
|
|
1281
|
+
// If so, we just want to discard this set of aggregations because they are
|
|
1282
|
+
// likely no longer valid for the newer query.
|
|
1283
|
+
const queryChangedSinceFetch =
|
|
1284
|
+
facetFetchQueryKey !== this.facetFetchQueryKey;
|
|
1285
|
+
if (queryChangedSinceFetch) return;
|
|
1186
1286
|
|
|
1187
1287
|
if (!success) {
|
|
1188
1288
|
const errorMsg = searchResponse?.error?.message;
|
|
@@ -1199,17 +1299,23 @@ export class CollectionBrowser
|
|
|
1199
1299
|
return;
|
|
1200
1300
|
}
|
|
1201
1301
|
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
// likely no longer valid for the newer query.
|
|
1205
|
-
const returnedUid = (success.request.clientParameters as any).uid;
|
|
1206
|
-
const queryChangedSinceFetch = returnedUid !== this.facetFetchQueryKey;
|
|
1207
|
-
if (queryChangedSinceFetch) return;
|
|
1302
|
+
const { aggregations, collectionTitles } = success.response;
|
|
1303
|
+
this.aggregations = aggregations;
|
|
1208
1304
|
|
|
1209
|
-
|
|
1305
|
+
if (collectionTitles) {
|
|
1306
|
+
this.collectionNameCache?.addKnownTitles(collectionTitles);
|
|
1307
|
+
} else if (this.aggregations?.collection) {
|
|
1308
|
+
this.collectionNameCache?.preloadIdentifiers(
|
|
1309
|
+
(this.aggregations.collection.buckets as Bucket[]).map(bucket =>
|
|
1310
|
+
bucket.key?.toString()
|
|
1311
|
+
)
|
|
1312
|
+
);
|
|
1313
|
+
}
|
|
1210
1314
|
|
|
1211
1315
|
this.fullYearsHistogramAggregation =
|
|
1212
1316
|
success?.response?.aggregations?.year_histogram;
|
|
1317
|
+
|
|
1318
|
+
this.facetsLoading = false;
|
|
1213
1319
|
}
|
|
1214
1320
|
|
|
1215
1321
|
private scrollToPage(pageNumber: number): Promise<void> {
|
|
@@ -1264,7 +1370,8 @@ export class CollectionBrowser
|
|
|
1264
1370
|
private pageFetchesInProgress: Record<string, Set<number>> = {};
|
|
1265
1371
|
|
|
1266
1372
|
async fetchPage(pageNumber: number) {
|
|
1267
|
-
|
|
1373
|
+
const trimmedQuery = this.baseQuery?.trim();
|
|
1374
|
+
if (!trimmedQuery) return;
|
|
1268
1375
|
if (!this.searchService) return;
|
|
1269
1376
|
|
|
1270
1377
|
// if we already have data, don't fetch again
|
|
@@ -1282,7 +1389,7 @@ export class CollectionBrowser
|
|
|
1282
1389
|
|
|
1283
1390
|
const sortParams = this.sortParam ? [this.sortParam] : [];
|
|
1284
1391
|
const params: SearchParams = {
|
|
1285
|
-
query:
|
|
1392
|
+
query: trimmedQuery,
|
|
1286
1393
|
page: pageNumber,
|
|
1287
1394
|
rows: this.pageSize,
|
|
1288
1395
|
sort: sortParams,
|
|
@@ -1296,6 +1403,12 @@ export class CollectionBrowser
|
|
|
1296
1403
|
);
|
|
1297
1404
|
const success = searchResponse?.success;
|
|
1298
1405
|
|
|
1406
|
+
// This is checking to see if the query has changed since the data was fetched.
|
|
1407
|
+
// If so, we just want to discard the data since there should be a new query
|
|
1408
|
+
// right behind it.
|
|
1409
|
+
const queryChangedSinceFetch = pageFetchQueryKey !== this.pageFetchQueryKey;
|
|
1410
|
+
if (queryChangedSinceFetch) return;
|
|
1411
|
+
|
|
1299
1412
|
if (!success) {
|
|
1300
1413
|
const errorMsg = searchResponse?.error?.message;
|
|
1301
1414
|
const detailMsg = searchResponse?.error?.details?.message;
|
|
@@ -1310,21 +1423,20 @@ export class CollectionBrowser
|
|
|
1310
1423
|
window?.Sentry?.captureMessage?.(this.queryErrorMessage, 'error');
|
|
1311
1424
|
}
|
|
1312
1425
|
|
|
1426
|
+
this.pageFetchesInProgress[pageFetchQueryKey]?.delete(pageNumber);
|
|
1427
|
+
this.searchResultsLoading = false;
|
|
1313
1428
|
return;
|
|
1314
1429
|
}
|
|
1315
1430
|
|
|
1316
|
-
// This is checking to see if the query has changed since the data was fetched.
|
|
1317
|
-
// If so, we just want to discard the data since there should be a new query
|
|
1318
|
-
// right behind it.
|
|
1319
|
-
const returnedUid = (success.request.clientParameters as any).uid;
|
|
1320
|
-
const queryChangedSinceFetch = returnedUid !== this.pageFetchQueryKey;
|
|
1321
|
-
if (queryChangedSinceFetch) return;
|
|
1322
|
-
|
|
1323
1431
|
this.totalResults = success.response.totalResults;
|
|
1324
1432
|
|
|
1325
|
-
const { results } = success.response;
|
|
1433
|
+
const { results, collectionTitles } = success.response;
|
|
1326
1434
|
if (results && results.length > 0) {
|
|
1327
|
-
|
|
1435
|
+
if (collectionTitles) {
|
|
1436
|
+
this.collectionNameCache?.addKnownTitles(collectionTitles);
|
|
1437
|
+
} else {
|
|
1438
|
+
this.preloadCollectionNames(results);
|
|
1439
|
+
}
|
|
1328
1440
|
this.updateDataSource(pageNumber, results);
|
|
1329
1441
|
}
|
|
1330
1442
|
|
|
@@ -1337,6 +1449,7 @@ export class CollectionBrowser
|
|
|
1337
1449
|
this.infiniteScroller.itemCount = this.totalResults;
|
|
1338
1450
|
}
|
|
1339
1451
|
}
|
|
1452
|
+
|
|
1340
1453
|
this.pageFetchesInProgress[pageFetchQueryKey]?.delete(pageNumber);
|
|
1341
1454
|
this.searchResultsLoading = false;
|
|
1342
1455
|
}
|
|
@@ -1442,12 +1555,14 @@ export class CollectionBrowser
|
|
|
1442
1555
|
private async fetchPrefixFilterBuckets(
|
|
1443
1556
|
filterType: PrefixFilterType
|
|
1444
1557
|
): Promise<Bucket[]> {
|
|
1445
|
-
|
|
1558
|
+
const trimmedQuery = this.baseQuery?.trim();
|
|
1559
|
+
if (!trimmedQuery) return [];
|
|
1446
1560
|
|
|
1447
1561
|
const filterAggregationKey = prefixFilterAggregationKeys[filterType];
|
|
1448
1562
|
const params: SearchParams = {
|
|
1449
|
-
query:
|
|
1563
|
+
query: trimmedQuery,
|
|
1450
1564
|
rows: 0,
|
|
1565
|
+
filters: this.filterMap,
|
|
1451
1566
|
// Only fetch the firstTitle or firstCreator aggregation
|
|
1452
1567
|
aggregations: { simpleParams: [filterAggregationKey] },
|
|
1453
1568
|
// Fetch all 26 letter buckets
|
|
@@ -1468,7 +1583,15 @@ export class CollectionBrowser
|
|
|
1468
1583
|
private async updatePrefixFilterCounts(
|
|
1469
1584
|
filterType: PrefixFilterType
|
|
1470
1585
|
): Promise<void> {
|
|
1586
|
+
const { facetFetchQueryKey } = this;
|
|
1471
1587
|
const buckets = await this.fetchPrefixFilterBuckets(filterType);
|
|
1588
|
+
|
|
1589
|
+
// Don't update the filter counts for an outdated query (if it has been changed
|
|
1590
|
+
// since we sent the request)
|
|
1591
|
+
const queryChangedSinceFetch =
|
|
1592
|
+
facetFetchQueryKey !== this.facetFetchQueryKey;
|
|
1593
|
+
if (queryChangedSinceFetch) return;
|
|
1594
|
+
|
|
1472
1595
|
// Unpack the aggregation buckets into a simple map like { 'A': 50, 'B': 25, ... }
|
|
1473
1596
|
this.prefixFilterCountMap = { ...this.prefixFilterCountMap }; // Clone the object to trigger an update
|
|
1474
1597
|
this.prefixFilterCountMap[filterType] = buckets.reduce(
|
|
@@ -1560,6 +1683,9 @@ export class CollectionBrowser
|
|
|
1560
1683
|
static styles = css`
|
|
1561
1684
|
:host {
|
|
1562
1685
|
display: block;
|
|
1686
|
+
|
|
1687
|
+
--leftColumnWidth: 18rem;
|
|
1688
|
+
--leftColumnPaddingRight: 2.5rem;
|
|
1563
1689
|
}
|
|
1564
1690
|
|
|
1565
1691
|
/**
|
|
@@ -1620,15 +1746,91 @@ export class CollectionBrowser
|
|
|
1620
1746
|
}
|
|
1621
1747
|
|
|
1622
1748
|
#left-column {
|
|
1623
|
-
width: 18rem;
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
padding-
|
|
1749
|
+
width: var(--leftColumnWidth, 18rem);
|
|
1750
|
+
/* Prevents Safari from shrinking col at first draw */
|
|
1751
|
+
min-width: var(--leftColumnWidth, 18rem);
|
|
1752
|
+
padding-top: 0;
|
|
1753
|
+
/* Reduced padding by 0.2rem to add the invisible border in the rule below */
|
|
1754
|
+
padding-right: calc(var(--leftColumnPaddingRight, 2.5rem) - 0.2rem);
|
|
1755
|
+
border-right: 0.2rem solid transparent; /* Pads to the right of the scrollbar a bit */
|
|
1627
1756
|
z-index: 1;
|
|
1628
1757
|
}
|
|
1629
1758
|
|
|
1759
|
+
.desktop #left-column {
|
|
1760
|
+
top: 0;
|
|
1761
|
+
position: sticky;
|
|
1762
|
+
height: calc(100vh - 2rem);
|
|
1763
|
+
max-height: calc(100vh - 2rem);
|
|
1764
|
+
overflow-x: hidden;
|
|
1765
|
+
overflow-y: scroll;
|
|
1766
|
+
|
|
1767
|
+
/*
|
|
1768
|
+
* Firefox doesn't support any of the -webkit-scrollbar stuff below, but
|
|
1769
|
+
* does at least give us a tiny bit of control over width & color.
|
|
1770
|
+
*/
|
|
1771
|
+
scrollbar-width: thin;
|
|
1772
|
+
scrollbar-color: transparent transparent;
|
|
1773
|
+
}
|
|
1774
|
+
.desktop #left-column:hover {
|
|
1775
|
+
scrollbar-color: auto;
|
|
1776
|
+
}
|
|
1630
1777
|
.desktop #left-column::-webkit-scrollbar {
|
|
1631
|
-
|
|
1778
|
+
appearance: none;
|
|
1779
|
+
width: 6px;
|
|
1780
|
+
}
|
|
1781
|
+
.desktop #left-column::-webkit-scrollbar-button {
|
|
1782
|
+
height: 3px;
|
|
1783
|
+
background: transparent;
|
|
1784
|
+
}
|
|
1785
|
+
.desktop #left-column::-webkit-scrollbar-corner {
|
|
1786
|
+
background: transparent;
|
|
1787
|
+
}
|
|
1788
|
+
.desktop #left-column::-webkit-scrollbar-thumb {
|
|
1789
|
+
border-radius: 4px;
|
|
1790
|
+
}
|
|
1791
|
+
.desktop #left-column:hover::-webkit-scrollbar-thumb {
|
|
1792
|
+
background: rgba(0, 0, 0, 0.15);
|
|
1793
|
+
}
|
|
1794
|
+
.desktop #left-column:hover::-webkit-scrollbar-thumb:hover {
|
|
1795
|
+
background: rgba(0, 0, 0, 0.2);
|
|
1796
|
+
}
|
|
1797
|
+
.desktop #left-column:hover::-webkit-scrollbar-thumb:active {
|
|
1798
|
+
background: rgba(0, 0, 0, 0.3);
|
|
1799
|
+
}
|
|
1800
|
+
|
|
1801
|
+
#facets-bottom-fade {
|
|
1802
|
+
background: linear-gradient(
|
|
1803
|
+
to bottom,
|
|
1804
|
+
#f5f5f700 0%,
|
|
1805
|
+
#f5f5f7c0 50%,
|
|
1806
|
+
#f5f5f7 80%,
|
|
1807
|
+
#f5f5f7 100%
|
|
1808
|
+
);
|
|
1809
|
+
position: fixed;
|
|
1810
|
+
bottom: 0;
|
|
1811
|
+
height: 50px;
|
|
1812
|
+
/* Wide enough to cover the content, but leave the scrollbar uncovered */
|
|
1813
|
+
width: calc(
|
|
1814
|
+
var(--leftColumnWidth) + var(--leftColumnPaddingRight) - 10px
|
|
1815
|
+
);
|
|
1816
|
+
z-index: 2;
|
|
1817
|
+
pointer-events: none;
|
|
1818
|
+
transition: height 0.1s ease;
|
|
1819
|
+
}
|
|
1820
|
+
#facets-bottom-fade.hidden {
|
|
1821
|
+
height: 0;
|
|
1822
|
+
}
|
|
1823
|
+
|
|
1824
|
+
.desktop #left-column-scroll-sentinel {
|
|
1825
|
+
width: 1px;
|
|
1826
|
+
height: 100vh;
|
|
1827
|
+
background: transparent;
|
|
1828
|
+
}
|
|
1829
|
+
|
|
1830
|
+
.desktop #facets-scroll-sentinel {
|
|
1831
|
+
width: 1px;
|
|
1832
|
+
height: 1px;
|
|
1833
|
+
background: transparent;
|
|
1632
1834
|
}
|
|
1633
1835
|
|
|
1634
1836
|
.mobile #left-column {
|
|
@@ -1636,21 +1838,16 @@ export class CollectionBrowser
|
|
|
1636
1838
|
padding: 0;
|
|
1637
1839
|
}
|
|
1638
1840
|
|
|
1639
|
-
.desktop #left-column {
|
|
1640
|
-
top: 0;
|
|
1641
|
-
position: sticky;
|
|
1642
|
-
max-height: 100vh;
|
|
1643
|
-
overflow: scroll;
|
|
1644
|
-
-ms-overflow-style: none; /* hide scrollbar IE and Edge */
|
|
1645
|
-
scrollbar-width: none; /* hide scrollbar Firefox */
|
|
1646
|
-
}
|
|
1647
|
-
|
|
1648
1841
|
#mobile-header-container {
|
|
1649
1842
|
display: flex;
|
|
1650
1843
|
justify-content: space-between;
|
|
1651
1844
|
align-items: center;
|
|
1652
1845
|
}
|
|
1653
1846
|
|
|
1847
|
+
.desktop #mobile-header-container {
|
|
1848
|
+
padding-top: 2rem;
|
|
1849
|
+
}
|
|
1850
|
+
|
|
1654
1851
|
#facets-container {
|
|
1655
1852
|
position: relative;
|
|
1656
1853
|
max-height: 0;
|