@internetarchive/collection-browser 0.4.16-alpha.9 → 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 +264 -110
- 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 +294 -112
- 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
package/src/app-root.ts
CHANGED
|
@@ -548,6 +548,18 @@ export class AppRoot extends LitElement {
|
|
|
548
548
|
--facet-row-border-bottom: 1px solid blue;
|
|
549
549
|
}
|
|
550
550
|
|
|
551
|
+
collection-browser {
|
|
552
|
+
/* Same as production */
|
|
553
|
+
max-width: 135rem;
|
|
554
|
+
margin: auto;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
#collection-browser-container {
|
|
558
|
+
/* Same as production */
|
|
559
|
+
padding-left: 0.5rem;
|
|
560
|
+
margin-bottom: 2rem;
|
|
561
|
+
}
|
|
562
|
+
|
|
551
563
|
#base-query-field {
|
|
552
564
|
width: 300px;
|
|
553
565
|
}
|
|
@@ -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,12 @@ export class CollectionBrowser
|
|
|
1199
1299
|
return;
|
|
1200
1300
|
}
|
|
1201
1301
|
|
|
1202
|
-
// This is checking to see if the query has changed since the data was fetched.
|
|
1203
|
-
// If so, we just want to discard this set of aggregations because they are
|
|
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;
|
|
1208
|
-
|
|
1209
1302
|
this.aggregations = success?.response.aggregations;
|
|
1210
1303
|
|
|
1211
1304
|
this.fullYearsHistogramAggregation =
|
|
1212
1305
|
success?.response?.aggregations?.year_histogram;
|
|
1306
|
+
|
|
1307
|
+
this.facetsLoading = false;
|
|
1213
1308
|
}
|
|
1214
1309
|
|
|
1215
1310
|
private scrollToPage(pageNumber: number): Promise<void> {
|
|
@@ -1264,7 +1359,8 @@ export class CollectionBrowser
|
|
|
1264
1359
|
private pageFetchesInProgress: Record<string, Set<number>> = {};
|
|
1265
1360
|
|
|
1266
1361
|
async fetchPage(pageNumber: number) {
|
|
1267
|
-
|
|
1362
|
+
const trimmedQuery = this.baseQuery?.trim();
|
|
1363
|
+
if (!trimmedQuery) return;
|
|
1268
1364
|
if (!this.searchService) return;
|
|
1269
1365
|
|
|
1270
1366
|
// if we already have data, don't fetch again
|
|
@@ -1282,7 +1378,7 @@ export class CollectionBrowser
|
|
|
1282
1378
|
|
|
1283
1379
|
const sortParams = this.sortParam ? [this.sortParam] : [];
|
|
1284
1380
|
const params: SearchParams = {
|
|
1285
|
-
query:
|
|
1381
|
+
query: trimmedQuery,
|
|
1286
1382
|
page: pageNumber,
|
|
1287
1383
|
rows: this.pageSize,
|
|
1288
1384
|
sort: sortParams,
|
|
@@ -1296,6 +1392,12 @@ export class CollectionBrowser
|
|
|
1296
1392
|
);
|
|
1297
1393
|
const success = searchResponse?.success;
|
|
1298
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
|
+
|
|
1299
1401
|
if (!success) {
|
|
1300
1402
|
const errorMsg = searchResponse?.error?.message;
|
|
1301
1403
|
const detailMsg = searchResponse?.error?.details?.message;
|
|
@@ -1310,16 +1412,11 @@ export class CollectionBrowser
|
|
|
1310
1412
|
window?.Sentry?.captureMessage?.(this.queryErrorMessage, 'error');
|
|
1311
1413
|
}
|
|
1312
1414
|
|
|
1415
|
+
this.pageFetchesInProgress[pageFetchQueryKey]?.delete(pageNumber);
|
|
1416
|
+
this.searchResultsLoading = false;
|
|
1313
1417
|
return;
|
|
1314
1418
|
}
|
|
1315
1419
|
|
|
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
1420
|
this.totalResults = success.response.totalResults;
|
|
1324
1421
|
|
|
1325
1422
|
const { results } = success.response;
|
|
@@ -1337,6 +1434,7 @@ export class CollectionBrowser
|
|
|
1337
1434
|
this.infiniteScroller.itemCount = this.totalResults;
|
|
1338
1435
|
}
|
|
1339
1436
|
}
|
|
1437
|
+
|
|
1340
1438
|
this.pageFetchesInProgress[pageFetchQueryKey]?.delete(pageNumber);
|
|
1341
1439
|
this.searchResultsLoading = false;
|
|
1342
1440
|
}
|
|
@@ -1442,12 +1540,14 @@ export class CollectionBrowser
|
|
|
1442
1540
|
private async fetchPrefixFilterBuckets(
|
|
1443
1541
|
filterType: PrefixFilterType
|
|
1444
1542
|
): Promise<Bucket[]> {
|
|
1445
|
-
|
|
1543
|
+
const trimmedQuery = this.baseQuery?.trim();
|
|
1544
|
+
if (!trimmedQuery) return [];
|
|
1446
1545
|
|
|
1447
1546
|
const filterAggregationKey = prefixFilterAggregationKeys[filterType];
|
|
1448
1547
|
const params: SearchParams = {
|
|
1449
|
-
query:
|
|
1548
|
+
query: trimmedQuery,
|
|
1450
1549
|
rows: 0,
|
|
1550
|
+
filters: this.filterMap,
|
|
1451
1551
|
// Only fetch the firstTitle or firstCreator aggregation
|
|
1452
1552
|
aggregations: { simpleParams: [filterAggregationKey] },
|
|
1453
1553
|
// Fetch all 26 letter buckets
|
|
@@ -1468,7 +1568,15 @@ export class CollectionBrowser
|
|
|
1468
1568
|
private async updatePrefixFilterCounts(
|
|
1469
1569
|
filterType: PrefixFilterType
|
|
1470
1570
|
): Promise<void> {
|
|
1571
|
+
const { facetFetchQueryKey } = this;
|
|
1471
1572
|
const buckets = await this.fetchPrefixFilterBuckets(filterType);
|
|
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
|
+
|
|
1472
1580
|
// Unpack the aggregation buckets into a simple map like { 'A': 50, 'B': 25, ... }
|
|
1473
1581
|
this.prefixFilterCountMap = { ...this.prefixFilterCountMap }; // Clone the object to trigger an update
|
|
1474
1582
|
this.prefixFilterCountMap[filterType] = buckets.reduce(
|
|
@@ -1560,6 +1668,9 @@ export class CollectionBrowser
|
|
|
1560
1668
|
static styles = css`
|
|
1561
1669
|
:host {
|
|
1562
1670
|
display: block;
|
|
1671
|
+
|
|
1672
|
+
--leftColumnWidth: 18rem;
|
|
1673
|
+
--leftColumnPaddingRight: 2.5rem;
|
|
1563
1674
|
}
|
|
1564
1675
|
|
|
1565
1676
|
/**
|
|
@@ -1620,15 +1731,91 @@ export class CollectionBrowser
|
|
|
1620
1731
|
}
|
|
1621
1732
|
|
|
1622
1733
|
#left-column {
|
|
1623
|
-
width: 18rem;
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
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 */
|
|
1627
1741
|
z-index: 1;
|
|
1628
1742
|
}
|
|
1629
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
|
+
}
|
|
1630
1762
|
.desktop #left-column::-webkit-scrollbar {
|
|
1631
|
-
|
|
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;
|
|
1632
1819
|
}
|
|
1633
1820
|
|
|
1634
1821
|
.mobile #left-column {
|
|
@@ -1636,21 +1823,16 @@ export class CollectionBrowser
|
|
|
1636
1823
|
padding: 0;
|
|
1637
1824
|
}
|
|
1638
1825
|
|
|
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
1826
|
#mobile-header-container {
|
|
1649
1827
|
display: flex;
|
|
1650
1828
|
justify-content: space-between;
|
|
1651
1829
|
align-items: center;
|
|
1652
1830
|
}
|
|
1653
1831
|
|
|
1832
|
+
.desktop #mobile-header-container {
|
|
1833
|
+
padding-top: 2rem;
|
|
1834
|
+
}
|
|
1835
|
+
|
|
1654
1836
|
#facets-container {
|
|
1655
1837
|
position: relative;
|
|
1656
1838
|
max-height: 0;
|