@internetarchive/collection-browser 0.3.2-alpha.5 → 0.3.3-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/dist/src/collection-browser.js +9 -0
  2. package/dist/src/collection-browser.js.map +1 -1
  3. package/dist/src/restoration-state-handler.d.ts +2 -1
  4. package/dist/src/restoration-state-handler.js +10 -0
  5. package/dist/src/restoration-state-handler.js.map +1 -1
  6. package/dist/src/tiles/image-block.js +4 -8
  7. package/dist/src/tiles/image-block.js.map +1 -1
  8. package/dist/src/tiles/list/tile-list-compact.js +1 -0
  9. package/dist/src/tiles/list/tile-list-compact.js.map +1 -1
  10. package/dist/src/tiles/list/tile-list.js +1 -0
  11. package/dist/src/tiles/list/tile-list.js.map +1 -1
  12. package/dist/src/tiles/overlay/icon-overlay.js +3 -4
  13. package/dist/src/tiles/overlay/icon-overlay.js.map +1 -1
  14. package/dist/test/collection-browser.test.js +28 -2
  15. package/dist/test/collection-browser.test.js.map +1 -1
  16. package/dist/test/image-block.test.d.ts +1 -0
  17. package/dist/test/image-block.test.js +79 -0
  18. package/dist/test/image-block.test.js.map +1 -0
  19. package/dist/test/tiles/list/tile-list-compact.test.d.ts +1 -0
  20. package/dist/test/tiles/list/tile-list-compact.test.js +31 -0
  21. package/dist/test/tiles/list/tile-list-compact.test.js.map +1 -0
  22. package/package.json +1 -1
  23. package/src/collection-browser.ts +24 -0
  24. package/src/restoration-state-handler.ts +19 -1
  25. package/src/tiles/image-block.ts +7 -10
  26. package/src/tiles/list/tile-list-compact.ts +1 -0
  27. package/src/tiles/list/tile-list.ts +1 -0
  28. package/src/tiles/overlay/icon-overlay.ts +3 -4
  29. package/test/collection-browser.test.ts +31 -2
  30. package/test/image-block.test.ts +86 -0
  31. package/test/tiles/list/tile-list-compact.test.ts +38 -0
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "description": "The Internet Archive Collection Browser.",
4
4
  "license": "AGPL-3.0-only",
5
5
  "author": "Internet Archive",
6
- "version": "0.3.2-alpha.5",
6
+ "version": "0.3.3-alpha.1",
7
7
  "main": "dist/index.js",
8
8
  "module": "dist/index.js",
9
9
  "scripts": {
@@ -619,6 +619,14 @@ export class CollectionBrowser
619
619
  }
620
620
 
621
621
  updated(changed: PropertyValues) {
622
+ if (changed.has('baseQuery') || changed.has('searchType'))
623
+ console.log(
624
+ 'updated',
625
+ changed,
626
+ changed.get('baseQuery'),
627
+ changed.get('searchType'),
628
+ this.baseImageUrl
629
+ );
622
630
  if (
623
631
  changed.has('displayMode') ||
624
632
  changed.has('baseNavigationUrl') ||
@@ -760,6 +768,7 @@ export class CollectionBrowser
760
768
  private previousQueryKey?: string;
761
769
 
762
770
  private async handleQueryChange() {
771
+ console.log('handleQueryChange', this.baseQuery);
763
772
  // only reset if the query has actually changed
764
773
  if (!this.searchService || this.pageFetchQueryKey === this.previousQueryKey)
765
774
  return;
@@ -775,6 +784,7 @@ export class CollectionBrowser
775
784
  this.initialQueryChangeHappened = true;
776
785
  // if the query changed as part of a window.history pop event, we don't want to
777
786
  // persist the state because it overwrites the forward history
787
+
778
788
  if (!this.historyPopOccurred) {
779
789
  this.persistState();
780
790
  this.historyPopOccurred = false;
@@ -809,7 +819,14 @@ export class CollectionBrowser
809
819
 
810
820
  private restoreState() {
811
821
  const restorationState = this.restorationStateHandler.getRestorationState();
822
+ console.log(
823
+ 'restoring state',
824
+ restorationState.baseQuery,
825
+ restorationState.searchType
826
+ );
812
827
  this.displayMode = restorationState.displayMode;
828
+ if (restorationState.searchType != null)
829
+ this.searchType = restorationState.searchType;
813
830
  this.selectedSort = restorationState.selectedSort ?? SortField.relevance;
814
831
  this.sortDirection = restorationState.sortDirection ?? null;
815
832
  this.selectedTitleFilter = restorationState.selectedTitleFilter ?? null;
@@ -831,6 +848,7 @@ export class CollectionBrowser
831
848
  private persistState() {
832
849
  const restorationState: RestorationState = {
833
850
  displayMode: this.displayMode,
851
+ searchType: this.searchType,
834
852
  sortParam: this.sortParam ?? undefined,
835
853
  selectedSort: this.selectedSort,
836
854
  sortDirection: this.sortDirection ?? undefined,
@@ -845,6 +863,11 @@ export class CollectionBrowser
845
863
  selectedTitleFilter: this.selectedTitleFilter ?? undefined,
846
864
  selectedCreatorFilter: this.selectedCreatorFilter ?? undefined,
847
865
  };
866
+ console.log(
867
+ 'persisting state',
868
+ restorationState.baseQuery,
869
+ restorationState.searchType
870
+ );
848
871
  this.restorationStateHandler.persistState(restorationState);
849
872
  }
850
873
 
@@ -1070,6 +1093,7 @@ export class CollectionBrowser
1070
1093
  private pageFetchesInProgress: Record<string, Set<number>> = {};
1071
1094
 
1072
1095
  async fetchPage(pageNumber: number) {
1096
+ console.log('fetchPage', this.baseQuery);
1073
1097
  if (!this.fullQuery) return;
1074
1098
 
1075
1099
  // if we already have data, don't fetch again
@@ -1,4 +1,8 @@
1
- import type { SortDirection, SortParam } from '@internetarchive/search-service';
1
+ import {
2
+ SearchType,
3
+ SortDirection,
4
+ SortParam,
5
+ } from '@internetarchive/search-service';
2
6
  import { getCookie, setCookie } from 'typescript-cookie';
3
7
  import {
4
8
  MetadataFieldToSortField,
@@ -14,6 +18,7 @@ import {
14
18
 
15
19
  export interface RestorationState {
16
20
  displayMode?: CollectionDisplayMode;
21
+ searchType?: SearchType;
17
22
  sortParam?: SortParam;
18
23
  selectedSort?: SortField;
19
24
  sortDirection?: SortDirection;
@@ -87,12 +92,20 @@ export class RestorationStateHandler
87
92
  private persistQueryStateToUrl(state: RestorationState) {
88
93
  const url = new URL(window.location.href);
89
94
  const { searchParams } = url;
95
+ searchParams.delete('sin');
90
96
  searchParams.delete('sort');
91
97
  searchParams.delete('query');
92
98
  searchParams.delete('page');
93
99
  searchParams.delete('and[]');
94
100
  searchParams.delete('not[]');
95
101
 
102
+ if (state.searchType) {
103
+ searchParams.set(
104
+ 'sin',
105
+ state.searchType === SearchType.FULLTEXT ? 'TXT' : ''
106
+ );
107
+ }
108
+
96
109
  if (state.sortParam) {
97
110
  const prefix = state.sortParam.direction === 'desc' ? '-' : '';
98
111
  searchParams.set('sort', `${prefix}${state.sortParam.field}`);
@@ -155,6 +168,7 @@ export class RestorationStateHandler
155
168
 
156
169
  private loadQueryStateFromUrl(): RestorationState {
157
170
  const url = new URL(window.location.href);
171
+ const searchInside = url.searchParams.get('sin');
158
172
  const pageNumber = url.searchParams.get('page');
159
173
  const searchQuery = url.searchParams.get('query');
160
174
  const sortQuery = url.searchParams.get('sort');
@@ -173,6 +187,10 @@ export class RestorationStateHandler
173
187
  },
174
188
  };
175
189
 
190
+ if (searchInside) {
191
+ restorationState.searchType =
192
+ searchInside === 'TXT' ? SearchType.FULLTEXT : undefined; // No explicit metadata sin
193
+ }
176
194
  if (pageNumber) {
177
195
  const parsed = parseInt(pageNumber, 10);
178
196
  restorationState.currentPage = parsed;
@@ -22,9 +22,8 @@ export class ImageBlock extends LitElement {
22
22
  @property({ type: String }) viewSize: string = 'desktop';
23
23
 
24
24
  render() {
25
- if (!this.model?.identifier) {
26
- return nothing;
27
- }
25
+ if (!this.model?.identifier) return nothing;
26
+
28
27
  return html`
29
28
  <div class=${classMap(this.baseClass)}>
30
29
  <item-image
@@ -52,9 +51,9 @@ export class ImageBlock extends LitElement {
52
51
  private get iconOverlayTemplate() {
53
52
  if (!this.isListTile) return nothing;
54
53
 
55
- if (!this.model?.loginRequired && !this.model?.contentWarning) {
54
+ if (!this.model?.loginRequired && !this.model?.contentWarning)
56
55
  return nothing;
57
- }
56
+
58
57
  return html`
59
58
  <icon-overlay
60
59
  .loggedIn=${this.loggedIn}
@@ -65,13 +64,11 @@ export class ImageBlock extends LitElement {
65
64
  }
66
65
 
67
66
  private get textOverlayTemplate() {
68
- if (this.isListTile) {
69
- return nothing;
70
- }
67
+ if (this.isListTile) return nothing;
71
68
 
72
- if (!this.model?.loginRequired && !this.model?.contentWarning) {
69
+ if (!this.model?.loginRequired && !this.model?.contentWarning)
73
70
  return nothing;
74
- }
71
+
75
72
  return html`
76
73
  <text-overlay
77
74
  .loggedIn=${this.loggedIn}
@@ -38,6 +38,7 @@ export class TileListCompact extends LitElement {
38
38
  .isCompactTile=${true}
39
39
  .isListTile=${true}
40
40
  .viewSize=${this.classSize}
41
+ .loggedIn=${this.loggedIn}
41
42
  >
42
43
  </image-block>
43
44
  <div id="title">${DOMPurify.sanitize(this.model?.title ?? '')}</div>
@@ -125,6 +125,7 @@ export class TileList extends LitElement {
125
125
  .isCompactTile=${false}
126
126
  .isListTile=${true}
127
127
  .viewSize=${this.classSize}
128
+ .loggedIn=${this.loggedIn}
128
129
  >
129
130
  </image-block>
130
131
  `;
@@ -11,10 +11,9 @@ export class IconOverlay extends LitElement {
11
11
  @property({ type: Boolean }) loginRequired = false;
12
12
 
13
13
  render() {
14
- if (this.loginRequired && !this.loggedIn) {
15
- return html`${loginRequiredIcon} `;
16
- }
17
- return html`${restrictedIcon}`;
14
+ return this.loginRequired && !this.loggedIn
15
+ ? html`${loginRequiredIcon}`
16
+ : html`${restrictedIcon}`;
18
17
  }
19
18
 
20
19
  static get styles(): CSSResultGroup {
@@ -18,6 +18,21 @@ import { MockAnalyticsHandler } from './mocks/mock-analytics-handler';
18
18
  import { analyticsCategories } from '../src/utils/analytics-events';
19
19
 
20
20
  describe('Collection Browser', () => {
21
+ beforeEach(async () => {
22
+ // Apparently query params set by one test can bleed into other tests.
23
+ // Since collection browser restores its state from certain query params, we need
24
+ // to clear these before each test to ensure they run in isolation from one another.
25
+ const url = new URL(window.location.href);
26
+ const { searchParams } = url;
27
+ searchParams.delete('sin');
28
+ searchParams.delete('sort');
29
+ searchParams.delete('query');
30
+ searchParams.delete('page');
31
+ searchParams.delete('and[]');
32
+ searchParams.delete('not[]');
33
+ window.history.replaceState({}, '', url);
34
+ });
35
+
21
36
  it('clear existing filter for facets & sort-bar', async () => {
22
37
  const el = await fixture<CollectionBrowser>(
23
38
  html`<collection-browser></collection-browser>`
@@ -370,8 +385,11 @@ describe('Collection Browser', () => {
370
385
  });
371
386
 
372
387
  it('sets sort properties when user changes sort', async () => {
388
+ const searchService = new MockSearchService();
373
389
  const el = await fixture<CollectionBrowser>(
374
- html`<collection-browser></collection-browser>`
390
+ html`<collection-browser
391
+ .searchService=${searchService}
392
+ ></collection-browser>`
375
393
  );
376
394
 
377
395
  expect(el.selectedSort).to.equal(SortField.relevance);
@@ -397,10 +415,17 @@ describe('Collection Browser', () => {
397
415
  });
398
416
 
399
417
  it('scrolls to page', async () => {
418
+ const searchService = new MockSearchService();
400
419
  const el = await fixture<CollectionBrowser>(
401
- html`<collection-browser></collection-browser>`
420
+ html`<collection-browser
421
+ .searchService=${searchService}
422
+ ></collection-browser>`
402
423
  );
403
424
 
425
+ // Infinite scroller won't exist unless there's a base query
426
+ el.baseQuery = 'collection:foo';
427
+ await el.updateComplete;
428
+
404
429
  const infiniteScroller = el.shadowRoot?.querySelector(
405
430
  'infinite-scroller'
406
431
  ) as InfiniteScroller;
@@ -435,6 +460,10 @@ describe('Collection Browser', () => {
435
460
  );
436
461
  const infiniteScrollerRefreshSpy = sinon.spy();
437
462
 
463
+ // Infinite scroller won't exist unless there's a base query
464
+ el.baseQuery = 'collection:foo';
465
+ await el.updateComplete;
466
+
438
467
  const infiniteScroller = el.shadowRoot?.querySelector('infinite-scroller');
439
468
  (infiniteScroller as InfiniteScroller).reload = infiniteScrollerRefreshSpy;
440
469
  expect(infiniteScrollerRefreshSpy.called).to.be.false;
@@ -0,0 +1,86 @@
1
+ /* eslint-disable import/no-duplicates */
2
+ import { expect, fixture } from '@open-wc/testing';
3
+ import { html } from 'lit';
4
+ import type { ImageBlock } from '../src/tiles/image-block';
5
+
6
+ import '../src/tiles/image-block';
7
+
8
+ describe('Image block component', () => {
9
+ it('should render component grid display mode', async () => {
10
+ const el = await fixture<ImageBlock>(html`
11
+ <image-block
12
+ .model=${{
13
+ loggedInRequired: true,
14
+ contentWarning: true,
15
+ identifier: 'goody',
16
+ }}
17
+ .baseImageUrl=${'https://archive.org'}
18
+ .isCompactTile=${false}
19
+ .isListTile=${false}
20
+ .viewSize=${'grid'}
21
+ .loggedIn=${false}
22
+ >
23
+ </image-block>
24
+ `);
25
+
26
+ const viewSize = el.shadowRoot?.querySelector('.grid');
27
+ const itemImage = el.shadowRoot?.querySelector('item-image');
28
+ const textOverlay = el.shadowRoot?.querySelector('text-overlay');
29
+
30
+ expect(viewSize).to.exist;
31
+ expect(itemImage).to.exist;
32
+ expect(textOverlay).to.exist;
33
+ });
34
+
35
+ it('should render component list display mode', async () => {
36
+ const el = await fixture<ImageBlock>(html`
37
+ <image-block
38
+ .model=${{
39
+ loggedInRequired: true,
40
+ contentWarning: true,
41
+ identifier: 'goody',
42
+ }}
43
+ .baseImageUrl=${'https://archive.org'}
44
+ .isCompactTile=${false}
45
+ .isListTile=${true}
46
+ .viewSize=${'desktop'}
47
+ .loggedIn=${false}
48
+ >
49
+ </image-block>
50
+ `);
51
+
52
+ const viewSize = el.shadowRoot?.querySelector('.list.desktop');
53
+ const itemImage = el.shadowRoot?.querySelector('item-image');
54
+ const iconOverlay = el.shadowRoot?.querySelector('icon-overlay');
55
+
56
+ expect(viewSize).to.exist;
57
+ expect(itemImage).to.exist;
58
+ expect(iconOverlay).to.exist;
59
+ });
60
+
61
+ it('should render component compact display mode', async () => {
62
+ const el = await fixture<ImageBlock>(html`
63
+ <image-block
64
+ .model=${{
65
+ loggedInRequired: true,
66
+ contentWarning: true,
67
+ identifier: 'goody',
68
+ }}
69
+ .baseImageUrl=${'https://archive.org'}
70
+ .isCompactTile=${true}
71
+ .isListTile=${true}
72
+ .viewSize=${'desktop'}
73
+ .loggedIn=${false}
74
+ >
75
+ </image-block>
76
+ `);
77
+
78
+ const viewSize = el.shadowRoot?.querySelector('.list-compact.desktop');
79
+ const itemImage = el.shadowRoot?.querySelector('item-image');
80
+ const iconOverlay = el.shadowRoot?.querySelector('icon-overlay');
81
+
82
+ expect(viewSize).to.exist;
83
+ expect(itemImage).to.exist;
84
+ expect(iconOverlay).to.exist;
85
+ });
86
+ });
@@ -0,0 +1,38 @@
1
+ /* eslint-disable import/no-duplicates */
2
+ import { expect, fixture } from '@open-wc/testing';
3
+ import { html } from 'lit';
4
+ import type { TileListCompact } from '../../../src/tiles/list/tile-list-compact';
5
+
6
+ import '../../../src/tiles/list/tile-list-compact';
7
+
8
+ describe('List Tile Compact', () => {
9
+ it('should render initial component', async () => {
10
+ const el = await fixture<TileListCompact>(
11
+ html`<tile-list-compact></tile-list-compact>`
12
+ );
13
+
14
+ const listContainer = el.shadowRoot?.querySelector('#list-line');
15
+ const itemTitle = el.shadowRoot?.querySelector('#title');
16
+ const imageBlock = el.shadowRoot?.querySelector('image-block');
17
+ const itemIcon = el.shadowRoot?.querySelector('#icon');
18
+ const itemViews = el.shadowRoot?.querySelector('#views');
19
+
20
+ expect(listContainer).to.exist;
21
+ expect(itemTitle).to.exist;
22
+ expect(imageBlock).to.exist;
23
+ expect(itemIcon).to.exist;
24
+ expect(itemViews).to.exist;
25
+ });
26
+
27
+ it('should render with creator element with title', async () => {
28
+ const el = await fixture<TileListCompact>(html`
29
+ <tile-list-compact
30
+ .model=${{ creators: ['someone'] }}
31
+ ></tile-list-compact>
32
+ `);
33
+
34
+ const creator = el.shadowRoot?.querySelector('#creator');
35
+
36
+ expect(creator).to.exist;
37
+ });
38
+ });