@internetarchive/collection-browser 0.4.16-alpha.8 → 0.4.16
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 +262 -116
- 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 +41 -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/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 +1 -1
- package/src/app-root.ts +12 -0
- package/src/collection-browser.ts +300 -126
- 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 +57 -12
- package/test/collection-facets/facets-template.test.ts +98 -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}
|
|
@@ -469,7 +470,6 @@ export class CollectionBrowser
|
|
|
469
470
|
}
|
|
470
471
|
|
|
471
472
|
private selectedSortChanged(): void {
|
|
472
|
-
console.log('selectedSortChanged');
|
|
473
473
|
if (this.selectedSort === 'relevance') {
|
|
474
474
|
this.sortParam = null;
|
|
475
475
|
return;
|
|
@@ -481,7 +481,6 @@ export class CollectionBrowser
|
|
|
481
481
|
this.sortParam = { field: sortField, direction: this.sortDirection };
|
|
482
482
|
|
|
483
483
|
// Lazy-load the alphabet counts for title/creator sort bar as needed
|
|
484
|
-
console.log('will update prefix filters for current sort');
|
|
485
484
|
this.updatePrefixFiltersForCurrentSort();
|
|
486
485
|
}
|
|
487
486
|
|
|
@@ -499,9 +498,30 @@ export class CollectionBrowser
|
|
|
499
498
|
}
|
|
500
499
|
}
|
|
501
500
|
|
|
502
|
-
/**
|
|
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
|
|
503
523
|
* labels: 'start-<ToLetter>' | 'clear-<FromLetter>' | '<FromLetter>-<ToLetter>'
|
|
504
|
-
|
|
524
|
+
*/
|
|
505
525
|
private sendFilterByTitleAnalytics(prevSelectedLetter: string | null): void {
|
|
506
526
|
if (!prevSelectedLetter && !this.selectedTitleFilter) {
|
|
507
527
|
return;
|
|
@@ -517,15 +537,10 @@ export class CollectionBrowser
|
|
|
517
537
|
});
|
|
518
538
|
}
|
|
519
539
|
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
? `firstTitle:${this.selectedTitleFilter}`
|
|
523
|
-
: undefined;
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
/** Send Analytics when filtering by creator's first letter
|
|
540
|
+
/**
|
|
541
|
+
* Send Analytics when filtering by creator's first letter
|
|
527
542
|
* labels: 'start-<ToLetter>' | 'clear-<FromLetter>' | '<FromLetter>-<ToLetter>'
|
|
528
|
-
|
|
543
|
+
*/
|
|
529
544
|
private sendFilterByCreatorAnalytics(
|
|
530
545
|
prevSelectedLetter: string | null
|
|
531
546
|
): void {
|
|
@@ -543,26 +558,24 @@ export class CollectionBrowser
|
|
|
543
558
|
});
|
|
544
559
|
}
|
|
545
560
|
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
: undefined;
|
|
550
|
-
}
|
|
551
|
-
|
|
561
|
+
/**
|
|
562
|
+
* Handler for changes to which letter is selected in the title alphabet bar.
|
|
563
|
+
*/
|
|
552
564
|
private titleLetterSelected(
|
|
553
565
|
e: CustomEvent<{ selectedLetter: string | null }>
|
|
554
566
|
): void {
|
|
555
567
|
this.selectedCreatorFilter = null;
|
|
556
568
|
this.selectedTitleFilter = e.detail.selectedLetter;
|
|
557
|
-
this.selectedTitleLetterChanged();
|
|
558
569
|
}
|
|
559
570
|
|
|
571
|
+
/**
|
|
572
|
+
* Handler for changes to which letter is selected in the creator alphabet bar.
|
|
573
|
+
*/
|
|
560
574
|
private creatorLetterSelected(
|
|
561
575
|
e: CustomEvent<{ selectedLetter: string | null }>
|
|
562
576
|
): void {
|
|
563
577
|
this.selectedTitleFilter = null;
|
|
564
578
|
this.selectedCreatorFilter = e.detail.selectedLetter;
|
|
565
|
-
this.selectedCreatorLetterChanged();
|
|
566
579
|
}
|
|
567
580
|
|
|
568
581
|
private get mobileFacetsTemplate() {
|
|
@@ -605,13 +618,13 @@ export class CollectionBrowser
|
|
|
605
618
|
.selectedFacets=${this.selectedFacets}
|
|
606
619
|
.collectionNameCache=${this.collectionNameCache}
|
|
607
620
|
.showHistogramDatePicker=${this.showHistogramDatePicker}
|
|
608
|
-
.query=${this.
|
|
621
|
+
.query=${this.baseQuery}
|
|
609
622
|
.filterMap=${this.filterMap}
|
|
610
623
|
.modalManager=${this.modalManager}
|
|
611
624
|
?collapsableFacets=${this.mobileView}
|
|
612
625
|
?facetsLoading=${this.facetsLoading}
|
|
613
626
|
?fullYearAggregationLoading=${this.facetsLoading}
|
|
614
|
-
|
|
627
|
+
@facetClick=${this.facetClickHandler}
|
|
615
628
|
.analyticsHandler=${this.analyticsHandler}
|
|
616
629
|
>
|
|
617
630
|
</collection-facets>
|
|
@@ -671,7 +684,16 @@ export class CollectionBrowser
|
|
|
671
684
|
}
|
|
672
685
|
|
|
673
686
|
updated(changed: PropertyValues) {
|
|
674
|
-
|
|
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
|
+
|
|
675
697
|
if (
|
|
676
698
|
changed.has('displayMode') ||
|
|
677
699
|
changed.has('baseNavigationUrl') ||
|
|
@@ -732,13 +754,11 @@ export class CollectionBrowser
|
|
|
732
754
|
this.sendFilterByTitleAnalytics(
|
|
733
755
|
changed.get('selectedTitleFilter') as string
|
|
734
756
|
);
|
|
735
|
-
this.selectedTitleLetterChanged();
|
|
736
757
|
}
|
|
737
758
|
if (changed.has('selectedCreatorFilter')) {
|
|
738
759
|
this.sendFilterByCreatorAnalytics(
|
|
739
760
|
changed.get('selectedCreatorFilter') as string
|
|
740
761
|
);
|
|
741
|
-
this.selectedCreatorLetterChanged();
|
|
742
762
|
}
|
|
743
763
|
|
|
744
764
|
if (
|
|
@@ -777,6 +797,10 @@ export class CollectionBrowser
|
|
|
777
797
|
if (this.boundNavigationHandler) {
|
|
778
798
|
window.removeEventListener('popstate', this.boundNavigationHandler);
|
|
779
799
|
}
|
|
800
|
+
|
|
801
|
+
this.leftColIntersectionObserver?.disconnect();
|
|
802
|
+
this.facetsIntersectionObserver?.disconnect();
|
|
803
|
+
window.removeEventListener('resize', this.updateLeftColumnHeight);
|
|
780
804
|
}
|
|
781
805
|
|
|
782
806
|
handleResize(entry: ResizeObserverEntry): void {
|
|
@@ -788,8 +812,80 @@ export class CollectionBrowser
|
|
|
788
812
|
this.isResizeToMobile = true;
|
|
789
813
|
}
|
|
790
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);
|
|
791
844
|
}
|
|
792
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
|
+
}
|
|
861
|
+
}
|
|
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
|
+
|
|
793
889
|
private emitBaseQueryChanged() {
|
|
794
890
|
this.dispatchEvent(
|
|
795
891
|
new CustomEvent<{ baseQuery?: string }>('baseQueryChanged', {
|
|
@@ -955,6 +1051,11 @@ export class CollectionBrowser
|
|
|
955
1051
|
this.searchResultsLoading = false;
|
|
956
1052
|
}
|
|
957
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
|
+
*/
|
|
958
1059
|
private get filterMap(): FilterMap {
|
|
959
1060
|
const builder = new FilterMapBuilder();
|
|
960
1061
|
|
|
@@ -998,21 +1099,24 @@ export class CollectionBrowser
|
|
|
998
1099
|
}
|
|
999
1100
|
}
|
|
1000
1101
|
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
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
|
+
);
|
|
1013
1116
|
}
|
|
1014
1117
|
|
|
1015
|
-
|
|
1118
|
+
const filterMap = builder.build();
|
|
1119
|
+
return filterMap;
|
|
1016
1120
|
}
|
|
1017
1121
|
|
|
1018
1122
|
/** The full query, including year facets and date range clauses */
|
|
@@ -1034,22 +1138,6 @@ export class CollectionBrowser
|
|
|
1034
1138
|
return fullQuery.trim();
|
|
1035
1139
|
}
|
|
1036
1140
|
|
|
1037
|
-
/** The full query without any title/creator letter filters */
|
|
1038
|
-
private get fullQueryWithoutAlphaFilters(): string | undefined {
|
|
1039
|
-
if (!this.baseQuery) return undefined;
|
|
1040
|
-
let fullQuery = this.baseQuery.trim();
|
|
1041
|
-
|
|
1042
|
-
const { facetQuery, dateRangeQueryClause } = this;
|
|
1043
|
-
|
|
1044
|
-
if (facetQuery) {
|
|
1045
|
-
fullQuery += ` AND ${facetQuery}`;
|
|
1046
|
-
}
|
|
1047
|
-
if (dateRangeQueryClause) {
|
|
1048
|
-
fullQuery += ` AND ${dateRangeQueryClause}`;
|
|
1049
|
-
}
|
|
1050
|
-
return fullQuery.trim();
|
|
1051
|
-
}
|
|
1052
|
-
|
|
1053
1141
|
/**
|
|
1054
1142
|
* Generates a query string for the given facets
|
|
1055
1143
|
*
|
|
@@ -1140,35 +1228,39 @@ export class CollectionBrowser
|
|
|
1140
1228
|
this.selectedFacets = e.detail;
|
|
1141
1229
|
}
|
|
1142
1230
|
|
|
1143
|
-
facetClickHandler(
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
negative: boolean
|
|
1147
|
-
): void {
|
|
1231
|
+
facetClickHandler({
|
|
1232
|
+
detail: { key, state: facetState, negative },
|
|
1233
|
+
}: CustomEvent<FacetEventDetails>): void {
|
|
1148
1234
|
if (negative) {
|
|
1149
1235
|
this.analyticsHandler?.sendEvent({
|
|
1150
1236
|
category: this.searchContext,
|
|
1151
|
-
action:
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1237
|
+
action:
|
|
1238
|
+
facetState !== 'none'
|
|
1239
|
+
? analyticsActions.facetNegativeSelected
|
|
1240
|
+
: analyticsActions.facetNegativeDeselected,
|
|
1241
|
+
label: key,
|
|
1155
1242
|
});
|
|
1156
1243
|
} else {
|
|
1157
1244
|
this.analyticsHandler?.sendEvent({
|
|
1158
1245
|
category: this.searchContext,
|
|
1159
|
-
action:
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1246
|
+
action:
|
|
1247
|
+
facetState !== 'none'
|
|
1248
|
+
? analyticsActions.facetSelected
|
|
1249
|
+
: analyticsActions.facetDeselected,
|
|
1250
|
+
label: key,
|
|
1163
1251
|
});
|
|
1164
1252
|
}
|
|
1165
1253
|
}
|
|
1166
1254
|
|
|
1167
1255
|
private async fetchFacets() {
|
|
1168
|
-
|
|
1256
|
+
const trimmedQuery = this.baseQuery?.trim();
|
|
1257
|
+
if (!trimmedQuery) return;
|
|
1258
|
+
if (!this.searchService) return;
|
|
1259
|
+
|
|
1260
|
+
const { facetFetchQueryKey } = this;
|
|
1169
1261
|
|
|
1170
1262
|
const params: SearchParams = {
|
|
1171
|
-
query:
|
|
1263
|
+
query: trimmedQuery,
|
|
1172
1264
|
rows: 0,
|
|
1173
1265
|
filters: this.filterMap,
|
|
1174
1266
|
// Fetch a few extra buckets beyond the 6 we show, in case some get suppressed
|
|
@@ -1179,12 +1271,18 @@ export class CollectionBrowser
|
|
|
1179
1271
|
};
|
|
1180
1272
|
|
|
1181
1273
|
this.facetsLoading = true;
|
|
1182
|
-
const searchResponse = await this.searchService
|
|
1274
|
+
const searchResponse = await this.searchService.search(
|
|
1183
1275
|
params,
|
|
1184
1276
|
this.searchType
|
|
1185
1277
|
);
|
|
1186
1278
|
const success = searchResponse?.success;
|
|
1187
|
-
|
|
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;
|
|
1188
1286
|
|
|
1189
1287
|
if (!success) {
|
|
1190
1288
|
const errorMsg = searchResponse?.error?.message;
|
|
@@ -1201,17 +1299,12 @@ export class CollectionBrowser
|
|
|
1201
1299
|
return;
|
|
1202
1300
|
}
|
|
1203
1301
|
|
|
1204
|
-
// This is checking to see if the query has changed since the data was fetched.
|
|
1205
|
-
// If so, we just want to discard this set of aggregations because they are
|
|
1206
|
-
// likely no longer valid for the newer query.
|
|
1207
|
-
const returnedUid = (success.request.clientParameters as any).uid;
|
|
1208
|
-
const queryChangedSinceFetch = returnedUid !== this.facetFetchQueryKey;
|
|
1209
|
-
if (queryChangedSinceFetch) return;
|
|
1210
|
-
|
|
1211
1302
|
this.aggregations = success?.response.aggregations;
|
|
1212
1303
|
|
|
1213
1304
|
this.fullYearsHistogramAggregation =
|
|
1214
1305
|
success?.response?.aggregations?.year_histogram;
|
|
1306
|
+
|
|
1307
|
+
this.facetsLoading = false;
|
|
1215
1308
|
}
|
|
1216
1309
|
|
|
1217
1310
|
private scrollToPage(pageNumber: number): Promise<void> {
|
|
@@ -1266,7 +1359,9 @@ export class CollectionBrowser
|
|
|
1266
1359
|
private pageFetchesInProgress: Record<string, Set<number>> = {};
|
|
1267
1360
|
|
|
1268
1361
|
async fetchPage(pageNumber: number) {
|
|
1269
|
-
|
|
1362
|
+
const trimmedQuery = this.baseQuery?.trim();
|
|
1363
|
+
if (!trimmedQuery) return;
|
|
1364
|
+
if (!this.searchService) return;
|
|
1270
1365
|
|
|
1271
1366
|
// if we already have data, don't fetch again
|
|
1272
1367
|
if (this.dataSource[pageNumber]) return;
|
|
@@ -1283,7 +1378,7 @@ export class CollectionBrowser
|
|
|
1283
1378
|
|
|
1284
1379
|
const sortParams = this.sortParam ? [this.sortParam] : [];
|
|
1285
1380
|
const params: SearchParams = {
|
|
1286
|
-
query:
|
|
1381
|
+
query: trimmedQuery,
|
|
1287
1382
|
page: pageNumber,
|
|
1288
1383
|
rows: this.pageSize,
|
|
1289
1384
|
sort: sortParams,
|
|
@@ -1291,12 +1386,18 @@ export class CollectionBrowser
|
|
|
1291
1386
|
aggregations: { omit: true },
|
|
1292
1387
|
uid: this.pageFetchQueryKey,
|
|
1293
1388
|
};
|
|
1294
|
-
const searchResponse = await this.searchService
|
|
1389
|
+
const searchResponse = await this.searchService.search(
|
|
1295
1390
|
params,
|
|
1296
1391
|
this.searchType
|
|
1297
1392
|
);
|
|
1298
1393
|
const success = searchResponse?.success;
|
|
1299
1394
|
|
|
1395
|
+
// This is checking to see if the query has changed since the data was fetched.
|
|
1396
|
+
// If so, we just want to discard the data since there should be a new query
|
|
1397
|
+
// right behind it.
|
|
1398
|
+
const queryChangedSinceFetch = pageFetchQueryKey !== this.pageFetchQueryKey;
|
|
1399
|
+
if (queryChangedSinceFetch) return;
|
|
1400
|
+
|
|
1300
1401
|
if (!success) {
|
|
1301
1402
|
const errorMsg = searchResponse?.error?.message;
|
|
1302
1403
|
const detailMsg = searchResponse?.error?.details?.message;
|
|
@@ -1311,16 +1412,11 @@ export class CollectionBrowser
|
|
|
1311
1412
|
window?.Sentry?.captureMessage?.(this.queryErrorMessage, 'error');
|
|
1312
1413
|
}
|
|
1313
1414
|
|
|
1415
|
+
this.pageFetchesInProgress[pageFetchQueryKey]?.delete(pageNumber);
|
|
1416
|
+
this.searchResultsLoading = false;
|
|
1314
1417
|
return;
|
|
1315
1418
|
}
|
|
1316
1419
|
|
|
1317
|
-
// This is checking to see if the query has changed since the data was fetched.
|
|
1318
|
-
// If so, we just want to discard the data since there should be a new query
|
|
1319
|
-
// right behind it.
|
|
1320
|
-
const returnedUid = (success.request.clientParameters as any).uid;
|
|
1321
|
-
const queryChangedSinceFetch = returnedUid !== this.pageFetchQueryKey;
|
|
1322
|
-
if (queryChangedSinceFetch) return;
|
|
1323
|
-
|
|
1324
1420
|
this.totalResults = success.response.totalResults;
|
|
1325
1421
|
|
|
1326
1422
|
const { results } = success.response;
|
|
@@ -1338,6 +1434,7 @@ export class CollectionBrowser
|
|
|
1338
1434
|
this.infiniteScroller.itemCount = this.totalResults;
|
|
1339
1435
|
}
|
|
1340
1436
|
}
|
|
1437
|
+
|
|
1341
1438
|
this.pageFetchesInProgress[pageFetchQueryKey]?.delete(pageNumber);
|
|
1342
1439
|
this.searchResultsLoading = false;
|
|
1343
1440
|
}
|
|
@@ -1443,24 +1540,25 @@ export class CollectionBrowser
|
|
|
1443
1540
|
private async fetchPrefixFilterBuckets(
|
|
1444
1541
|
filterType: PrefixFilterType
|
|
1445
1542
|
): Promise<Bucket[]> {
|
|
1446
|
-
|
|
1447
|
-
if (!
|
|
1543
|
+
const trimmedQuery = this.baseQuery?.trim();
|
|
1544
|
+
if (!trimmedQuery) return [];
|
|
1448
1545
|
|
|
1449
1546
|
const filterAggregationKey = prefixFilterAggregationKeys[filterType];
|
|
1450
1547
|
const params: SearchParams = {
|
|
1451
|
-
query:
|
|
1548
|
+
query: trimmedQuery,
|
|
1452
1549
|
rows: 0,
|
|
1550
|
+
filters: this.filterMap,
|
|
1453
1551
|
// Only fetch the firstTitle or firstCreator aggregation
|
|
1454
1552
|
aggregations: { simpleParams: [filterAggregationKey] },
|
|
1455
1553
|
// Fetch all 26 letter buckets
|
|
1456
1554
|
aggregationsSize: 26,
|
|
1457
1555
|
};
|
|
1458
|
-
|
|
1556
|
+
|
|
1459
1557
|
const searchResponse = await this.searchService?.search(
|
|
1460
1558
|
params,
|
|
1461
1559
|
this.searchType
|
|
1462
1560
|
);
|
|
1463
|
-
|
|
1561
|
+
|
|
1464
1562
|
return (searchResponse?.success?.response?.aggregations?.[
|
|
1465
1563
|
filterAggregationKey
|
|
1466
1564
|
]?.buckets ?? []) as Bucket[];
|
|
@@ -1470,9 +1568,15 @@ export class CollectionBrowser
|
|
|
1470
1568
|
private async updatePrefixFilterCounts(
|
|
1471
1569
|
filterType: PrefixFilterType
|
|
1472
1570
|
): Promise<void> {
|
|
1473
|
-
|
|
1571
|
+
const { facetFetchQueryKey } = this;
|
|
1474
1572
|
const buckets = await this.fetchPrefixFilterBuckets(filterType);
|
|
1475
|
-
|
|
1573
|
+
|
|
1574
|
+
// Don't update the filter counts for an outdated query (if it has been changed
|
|
1575
|
+
// since we sent the request)
|
|
1576
|
+
const queryChangedSinceFetch =
|
|
1577
|
+
facetFetchQueryKey !== this.facetFetchQueryKey;
|
|
1578
|
+
if (queryChangedSinceFetch) return;
|
|
1579
|
+
|
|
1476
1580
|
// Unpack the aggregation buckets into a simple map like { 'A': 50, 'B': 25, ... }
|
|
1477
1581
|
this.prefixFilterCountMap = { ...this.prefixFilterCountMap }; // Clone the object to trigger an update
|
|
1478
1582
|
this.prefixFilterCountMap[filterType] = buckets.reduce(
|
|
@@ -1489,12 +1593,9 @@ export class CollectionBrowser
|
|
|
1489
1593
|
* provided it is one that permits prefix filtering. (If not, this does nothing).
|
|
1490
1594
|
*/
|
|
1491
1595
|
private async updatePrefixFiltersForCurrentSort(): Promise<void> {
|
|
1492
|
-
console.log('updatePrefixFiltersForCurrentSort');
|
|
1493
1596
|
if (['title', 'creator'].includes(this.selectedSort)) {
|
|
1494
|
-
console.log('sort is title or creator - will update')
|
|
1495
1597
|
const filterType = this.selectedSort as PrefixFilterType;
|
|
1496
1598
|
if (!this.prefixFilterCountMap[filterType]) {
|
|
1497
|
-
console.log('need new filters, updating');
|
|
1498
1599
|
this.updatePrefixFilterCounts(filterType);
|
|
1499
1600
|
}
|
|
1500
1601
|
}
|
|
@@ -1508,7 +1609,6 @@ export class CollectionBrowser
|
|
|
1508
1609
|
* Call this whenever the counts are invalidated (e.g., by a query change).
|
|
1509
1610
|
*/
|
|
1510
1611
|
private refreshLetterCounts(): void {
|
|
1511
|
-
console.log('refreshLetterCounts');
|
|
1512
1612
|
if (Object.keys(this.prefixFilterCountMap).length > 0) {
|
|
1513
1613
|
this.prefixFilterCountMap = {};
|
|
1514
1614
|
}
|
|
@@ -1568,6 +1668,9 @@ export class CollectionBrowser
|
|
|
1568
1668
|
static styles = css`
|
|
1569
1669
|
:host {
|
|
1570
1670
|
display: block;
|
|
1671
|
+
|
|
1672
|
+
--leftColumnWidth: 18rem;
|
|
1673
|
+
--leftColumnPaddingRight: 2.5rem;
|
|
1571
1674
|
}
|
|
1572
1675
|
|
|
1573
1676
|
/**
|
|
@@ -1628,15 +1731,91 @@ export class CollectionBrowser
|
|
|
1628
1731
|
}
|
|
1629
1732
|
|
|
1630
1733
|
#left-column {
|
|
1631
|
-
width: 18rem;
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
padding-
|
|
1734
|
+
width: var(--leftColumnWidth, 18rem);
|
|
1735
|
+
/* Prevents Safari from shrinking col at first draw */
|
|
1736
|
+
min-width: var(--leftColumnWidth, 18rem);
|
|
1737
|
+
padding-top: 0;
|
|
1738
|
+
/* Reduced padding by 0.2rem to add the invisible border in the rule below */
|
|
1739
|
+
padding-right: calc(var(--leftColumnPaddingRight, 2.5rem) - 0.2rem);
|
|
1740
|
+
border-right: 0.2rem solid transparent; /* Pads to the right of the scrollbar a bit */
|
|
1635
1741
|
z-index: 1;
|
|
1636
1742
|
}
|
|
1637
1743
|
|
|
1744
|
+
.desktop #left-column {
|
|
1745
|
+
top: 0;
|
|
1746
|
+
position: sticky;
|
|
1747
|
+
height: calc(100vh - 2rem);
|
|
1748
|
+
max-height: calc(100vh - 2rem);
|
|
1749
|
+
overflow-x: hidden;
|
|
1750
|
+
overflow-y: scroll;
|
|
1751
|
+
|
|
1752
|
+
/*
|
|
1753
|
+
* Firefox doesn't support any of the -webkit-scrollbar stuff below, but
|
|
1754
|
+
* does at least give us a tiny bit of control over width & color.
|
|
1755
|
+
*/
|
|
1756
|
+
scrollbar-width: thin;
|
|
1757
|
+
scrollbar-color: transparent transparent;
|
|
1758
|
+
}
|
|
1759
|
+
.desktop #left-column:hover {
|
|
1760
|
+
scrollbar-color: auto;
|
|
1761
|
+
}
|
|
1638
1762
|
.desktop #left-column::-webkit-scrollbar {
|
|
1639
|
-
|
|
1763
|
+
appearance: none;
|
|
1764
|
+
width: 6px;
|
|
1765
|
+
}
|
|
1766
|
+
.desktop #left-column::-webkit-scrollbar-button {
|
|
1767
|
+
height: 3px;
|
|
1768
|
+
background: transparent;
|
|
1769
|
+
}
|
|
1770
|
+
.desktop #left-column::-webkit-scrollbar-corner {
|
|
1771
|
+
background: transparent;
|
|
1772
|
+
}
|
|
1773
|
+
.desktop #left-column::-webkit-scrollbar-thumb {
|
|
1774
|
+
border-radius: 4px;
|
|
1775
|
+
}
|
|
1776
|
+
.desktop #left-column:hover::-webkit-scrollbar-thumb {
|
|
1777
|
+
background: rgba(0, 0, 0, 0.15);
|
|
1778
|
+
}
|
|
1779
|
+
.desktop #left-column:hover::-webkit-scrollbar-thumb:hover {
|
|
1780
|
+
background: rgba(0, 0, 0, 0.2);
|
|
1781
|
+
}
|
|
1782
|
+
.desktop #left-column:hover::-webkit-scrollbar-thumb:active {
|
|
1783
|
+
background: rgba(0, 0, 0, 0.3);
|
|
1784
|
+
}
|
|
1785
|
+
|
|
1786
|
+
#facets-bottom-fade {
|
|
1787
|
+
background: linear-gradient(
|
|
1788
|
+
to bottom,
|
|
1789
|
+
#f5f5f700 0%,
|
|
1790
|
+
#f5f5f7c0 50%,
|
|
1791
|
+
#f5f5f7 80%,
|
|
1792
|
+
#f5f5f7 100%
|
|
1793
|
+
);
|
|
1794
|
+
position: fixed;
|
|
1795
|
+
bottom: 0;
|
|
1796
|
+
height: 50px;
|
|
1797
|
+
/* Wide enough to cover the content, but leave the scrollbar uncovered */
|
|
1798
|
+
width: calc(
|
|
1799
|
+
var(--leftColumnWidth) + var(--leftColumnPaddingRight) - 10px
|
|
1800
|
+
);
|
|
1801
|
+
z-index: 2;
|
|
1802
|
+
pointer-events: none;
|
|
1803
|
+
transition: height 0.1s ease;
|
|
1804
|
+
}
|
|
1805
|
+
#facets-bottom-fade.hidden {
|
|
1806
|
+
height: 0;
|
|
1807
|
+
}
|
|
1808
|
+
|
|
1809
|
+
.desktop #left-column-scroll-sentinel {
|
|
1810
|
+
width: 1px;
|
|
1811
|
+
height: 100vh;
|
|
1812
|
+
background: transparent;
|
|
1813
|
+
}
|
|
1814
|
+
|
|
1815
|
+
.desktop #facets-scroll-sentinel {
|
|
1816
|
+
width: 1px;
|
|
1817
|
+
height: 1px;
|
|
1818
|
+
background: transparent;
|
|
1640
1819
|
}
|
|
1641
1820
|
|
|
1642
1821
|
.mobile #left-column {
|
|
@@ -1644,21 +1823,16 @@ export class CollectionBrowser
|
|
|
1644
1823
|
padding: 0;
|
|
1645
1824
|
}
|
|
1646
1825
|
|
|
1647
|
-
.desktop #left-column {
|
|
1648
|
-
top: 0;
|
|
1649
|
-
position: sticky;
|
|
1650
|
-
max-height: 100vh;
|
|
1651
|
-
overflow: scroll;
|
|
1652
|
-
-ms-overflow-style: none; /* hide scrollbar IE and Edge */
|
|
1653
|
-
scrollbar-width: none; /* hide scrollbar Firefox */
|
|
1654
|
-
}
|
|
1655
|
-
|
|
1656
1826
|
#mobile-header-container {
|
|
1657
1827
|
display: flex;
|
|
1658
1828
|
justify-content: space-between;
|
|
1659
1829
|
align-items: center;
|
|
1660
1830
|
}
|
|
1661
1831
|
|
|
1832
|
+
.desktop #mobile-header-container {
|
|
1833
|
+
padding-top: 2rem;
|
|
1834
|
+
}
|
|
1835
|
+
|
|
1662
1836
|
#facets-container {
|
|
1663
1837
|
position: relative;
|
|
1664
1838
|
max-height: 0;
|