@internetarchive/collection-browser 2.7.6-alpha1 → 2.7.6-alpha3

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/src/app-root.ts CHANGED
@@ -50,8 +50,6 @@ export class AppRoot extends LitElement {
50
50
 
51
51
  @state() private searchType: SearchType = SearchType.METADATA;
52
52
 
53
- @state() private itemRemovalError: String = '';
54
-
55
53
  @property({ type: Object, reflect: false }) latestAction?: AnalyticsEvent;
56
54
 
57
55
  @query('#base-query-field') private baseQueryField!: HTMLInputElement;
@@ -487,20 +485,13 @@ export class AppRoot extends LitElement {
487
485
  .modalManager=${this.modalManager}
488
486
  .analyticsHandler=${this.analyticsHandler}
489
487
  .pageContext=${'search'}
490
- .activeTabId=${'uploads'}
491
- .itemRemovalFailed=${''}
488
+ .activeTabId=${''}
492
489
  @visiblePageChanged=${this.visiblePageChanged}
493
490
  @baseQueryChanged=${this.baseQueryChanged}
494
491
  @searchTypeChanged=${this.searchTypeChanged}
495
492
  @manageModeChanged=${this.manageModeChanged}
496
- @itemRemovalRequested=${(e: CustomEvent) => {
497
- console.log('itemRemovalRequested: ', e.detail.items);
498
- setTimeout(() => {
499
- this.itemRemovalError = 'something has changed!';
500
- }, 1200);
501
- }}
502
- @itemManagerRequested=${(e: CustomEvent) =>
503
- console.log('itemManagerRequested: ', e.detail.items)}
493
+ @itemRemovalRequested=${this.handleItemRemovalRequest}
494
+ @itemManagerRequested=${this.handleItemManagerRequest}
504
495
  >
505
496
  ${this.toggleSlots
506
497
  ? html`<div slot="sortbar-left-slot">Sort Slot</div>`
@@ -715,6 +706,35 @@ export class AppRoot extends LitElement {
715
706
  if (manageCheckbox) manageCheckbox.checked = e.detail;
716
707
  }
717
708
 
709
+ /**
710
+ * Handler for item removal
711
+ */
712
+ private handleItemRemovalRequest(e: CustomEvent) {
713
+ setTimeout(() => {
714
+ console.log('itemRemovalRequested: ', e.detail.items);
715
+
716
+ // execute item-removal-service, and response is successfully deleted
717
+ const status = false;
718
+
719
+ if (status) {
720
+ // looking for success?
721
+ this.collectionBrowser.isManageView = false;
722
+ this.modalManager?.closeModal();
723
+ this.modalManager?.classList.remove('remove-items');
724
+ } else {
725
+ // looking for failure?
726
+ this.collectionBrowser.hasItemsDeleted = false;
727
+ }
728
+ }, 2000); // let's wait to see processing modal
729
+ }
730
+
731
+ /**
732
+ * Handler when item manage requested
733
+ */
734
+ private handleItemManagerRequest(e: CustomEvent) {
735
+ console.log('itemManagerRequested: ', e.detail.items);
736
+ }
737
+
718
738
  /**
719
739
  * Handler for when the dev panel's "Enable manage mode" checkbox is changed.
720
740
  */
@@ -723,7 +743,7 @@ export class AppRoot extends LitElement {
723
743
  this.collectionBrowser.isManageView = target.checked;
724
744
  this.collectionBrowser.manageViewLabel =
725
745
  'Select items to remove (customizable texts)';
726
- this.itemRemovalError = '';
746
+ this.collectionBrowser.hasItemsDeleted = true;
727
747
  }
728
748
 
729
749
  /**
@@ -44,6 +44,7 @@ import {
44
44
  SORT_OPTIONS,
45
45
  defaultProfileElementSorts,
46
46
  FacetLoadStrategy,
47
+ ManageableItem,
47
48
  } from './models';
48
49
  import {
49
50
  RestorationStateHandlerInterface,
@@ -244,7 +245,9 @@ export class CollectionBrowser
244
245
  /**
245
246
  * If item management UI active
246
247
  */
247
- @property({ type: Boolean }) isManageView = true;
248
+ @property({ type: Boolean }) isManageView = false;
249
+
250
+ @property({ type: Boolean }) hasItemsDeleted = true;
248
251
 
249
252
  @property({ type: String }) manageViewLabel = 'Select items to remove';
250
253
 
@@ -264,8 +267,6 @@ export class CollectionBrowser
264
267
  @property({ type: Object }) dataSource: CollectionBrowserDataSourceInterface =
265
268
  new CollectionBrowserDataSource(this, this.pageSize);
266
269
 
267
- @property({ type: String }) itemRemovalFailed = '';
268
-
269
270
  /**
270
271
  * The page that the consumer wants to load.
271
272
  */
@@ -782,10 +783,10 @@ export class CollectionBrowser
782
783
  .label=${this.manageViewLabel}
783
784
  .modalManager=${this.modalManager}
784
785
  .selectedItems=${this.dataSource.checkedTileModels}
785
- .itemRemovalFailed=${this.itemRemovalFailed}
786
+ .hasItemsDeleted=${this.hasItemsDeleted}
786
787
  showSelectAll
787
788
  showUnselectAll
788
- ?showManageButton=${this.pageContext === 'search'}
789
+ ?showItemManageButton=${this.pageContext === 'search'}
789
790
  ?removeAllowed=${this.dataSource.checkedTileModels.length !== 0}
790
791
  @removeItems=${this.handleRemoveItems}
791
792
  @manageItems=${this.handleManageItems}
@@ -804,14 +805,16 @@ export class CollectionBrowser
804
805
  * Emits an `itemRemovalRequested` event with all checked tile models.
805
806
  */
806
807
  private handleRemoveItems(): void {
808
+ this.hasItemsDeleted = true;
809
+
807
810
  this.dispatchEvent(
808
- new CustomEvent<{ items: String }>('itemRemovalRequested', {
811
+ new CustomEvent<{ items: ManageableItem[] }>('itemRemovalRequested', {
809
812
  detail: {
810
813
  items: this.dataSource.checkedTileModels.map(model => {
811
814
  const cloned = model.clone();
812
815
  cloned.dateStr = formatDate(model.datePublished, 'long');
813
- return cloned.identifier;
814
- }).join(','),
816
+ return cloned as ManageableItem;
817
+ }),
815
818
  },
816
819
  })
817
820
  );
@@ -822,12 +825,11 @@ export class CollectionBrowser
822
825
  */
823
826
  private handleManageItems(): void {
824
827
  this.dispatchEvent(
825
- new CustomEvent<{ items: String }>('itemManagerRequested', {
828
+ new CustomEvent<{ items: ManageableItem[] }>('itemManagerRequested', {
826
829
  detail: {
827
- items: this.dataSource.checkedTileModels
828
- .map(item => item.identifier)
829
- .filter(Boolean)
830
- .join(','),
830
+ items: this.dataSource.checkedTileModels.map(
831
+ model => model as ManageableItem
832
+ ),
831
833
  },
832
834
  })
833
835
  );
@@ -13,15 +13,10 @@ import {
13
13
  ModalConfig,
14
14
  type ModalManagerInterface,
15
15
  } from '@internetarchive/modal-manager';
16
+ import type { ManageableItem } from '../models';
16
17
  import iaButtonStyle from '../styles/ia-button';
17
18
  import './remove-items-modal-content';
18
19
 
19
- export interface ManageableItem {
20
- identifier: string;
21
- title?: string;
22
- dateStr?: string;
23
- }
24
-
25
20
  @customElement('manage-bar')
26
21
  export class ManageBar extends LitElement {
27
22
  /**
@@ -44,11 +39,6 @@ export class ManageBar extends LitElement {
44
39
  */
45
40
  @property({ type: Object }) selectedItems: Array<ManageableItem> = [];
46
41
 
47
- /**
48
- * Item removal failure message, if any
49
- */
50
- @property({ type: String }) itemRemovalFailed = '';
51
-
52
42
  /**
53
43
  * Whether to show the "Select All" button (default false)
54
44
  */
@@ -59,10 +49,15 @@ export class ManageBar extends LitElement {
59
49
  */
60
50
  @property({ type: Boolean }) showUnselectAll = false;
61
51
 
52
+ /**
53
+ * Whether item has deleted or not (default true)
54
+ */
55
+ @property({ type: Boolean }) hasItemsDeleted = true;
56
+
62
57
  /**
63
58
  * Whether to show "Item Manager the items" button (default false)
64
59
  */
65
- @property({ type: Boolean }) showManageButton = false;
60
+ @property({ type: Boolean }) showItemManageButton = false;
66
61
 
67
62
  /**
68
63
  * Whether to active delete button for selectable items
@@ -70,7 +65,7 @@ export class ManageBar extends LitElement {
70
65
  @property({ type: Boolean }) removeAllowed = false;
71
66
 
72
67
  updated(changed: PropertyValues): void {
73
- if (changed.has('itemRemovalFailed') && this.itemRemovalFailed !== '') {
68
+ if (changed.has('hasItemsDeleted') && !this.hasItemsDeleted) {
74
69
  this.showRemoveItemsErrorModal();
75
70
  }
76
71
  }
@@ -91,7 +86,7 @@ export class ManageBar extends LitElement {
91
86
  ${msg('Remove selected items')}
92
87
  </button>
93
88
  ${when(
94
- this.showManageButton,
89
+ this.showItemManageButton,
95
90
  () => html` <button
96
91
  class="ia-button warning"
97
92
  ?disabled=${!this.removeAllowed}
@@ -129,10 +124,6 @@ export class ManageBar extends LitElement {
129
124
  this.dispatchEvent(new CustomEvent('cancel'));
130
125
  }
131
126
 
132
- /**
133
- * Shows a modal dialog confirming the list of items to be removed
134
- * @param items Which items to list in the modal
135
- */
136
127
  private removeItemsClicked(): void {
137
128
  this.showRemoveItemsProcessingModal();
138
129
  this.dispatchEvent(new CustomEvent('removeItems'));
@@ -225,7 +216,9 @@ export class ManageBar extends LitElement {
225
216
  showHeaderLogo: false,
226
217
  closeOnBackdropClick: true,
227
218
  title: html`${msg('Error: unable to remove items')}`,
228
- message: html`${msg(this.itemRemovalFailed)}`,
219
+ message: html`${msg(
220
+ 'An error occurred while removing items. Please try again in a few minutes.'
221
+ )}`,
229
222
  });
230
223
 
231
224
  this.modalManager?.classList.add('remove-items');
@@ -2,12 +2,7 @@ import { LitElement, html, css, nothing, TemplateResult, CSSResult } from 'lit';
2
2
  import { customElement, property } from 'lit/decorators.js';
3
3
  import { msg } from '@lit/localize';
4
4
  import { map } from 'lit/directives/map.js';
5
-
6
- export interface ManageableItem {
7
- identifier: string;
8
- title?: string;
9
- date?: string;
10
- }
5
+ import type { ManageableItem } from '../models';
11
6
 
12
7
  @customElement('remove-items-modal-content')
13
8
  export class RemoveItemsModalContent extends LitElement {
package/src/models.ts CHANGED
@@ -675,3 +675,13 @@ export const suppressedCollections: Record<string, boolean> = {
675
675
  americana: true,
676
676
  toronto: true,
677
677
  };
678
+
679
+ /**
680
+ * A record of manageable item
681
+ */
682
+ export interface ManageableItem {
683
+ identifier: string;
684
+ title?: string;
685
+ dateStr?: string;
686
+ date?: string;
687
+ }
@@ -2,9 +2,14 @@
2
2
  import { expect, fixture } from '@open-wc/testing';
3
3
  import { html } from 'lit';
4
4
  import Sinon from 'sinon';
5
- import type { ManageBar } from '../../src/manage/manage-bar';
6
-
7
5
  import '../../src/manage/manage-bar';
6
+ import {
7
+ ModalManager,
8
+ ModalManagerInterface,
9
+ } from '@internetarchive/modal-manager';
10
+ import '@internetarchive/modal-manager';
11
+ import { msg } from '@lit/localize';
12
+ import type { ManageBar } from '../../src/manage/manage-bar';
8
13
 
9
14
  describe('Manage bar', () => {
10
15
  it('renders basic component', async () => {
@@ -38,7 +43,7 @@ describe('Manage bar', () => {
38
43
 
39
44
  it('render item manager button for /search/ page', async () => {
40
45
  const el = await fixture<ManageBar>(
41
- html`<manage-bar .pageContext=${'search'}></manage-bar>`
46
+ html`<manage-bar showItemManageButton></manage-bar>`
42
47
  );
43
48
  expect(el.shadowRoot?.querySelector('.ia-button.warning')).to.exist;
44
49
  });
@@ -84,21 +89,6 @@ describe('Manage bar', () => {
84
89
  expect(spy.callCount).to.equal(1);
85
90
  });
86
91
 
87
- it('emits event when Remove Items button clicked', async () => {
88
- const spy = Sinon.spy();
89
- const el = await fixture<ManageBar>(
90
- html`<manage-bar @removeItems=${spy} removeAllowed></manage-bar>`
91
- );
92
-
93
- const removeItemsBtn = el.shadowRoot?.querySelector(
94
- '.ia-button.danger'
95
- ) as HTMLButtonElement;
96
- expect(removeItemsBtn).to.exist;
97
-
98
- removeItemsBtn.click();
99
- expect(spy.callCount).to.equal(1);
100
- });
101
-
102
92
  it('emits event when Select All button clicked', async () => {
103
93
  const spy = Sinon.spy();
104
94
  const el = await fixture<ManageBar>(
@@ -128,4 +118,37 @@ describe('Manage bar', () => {
128
118
  unselectAllBtn.click();
129
119
  expect(spy.callCount).to.equal(1);
130
120
  });
121
+
122
+ it('opens the remove items modal when showRemoveItemsModal is clicked', async () => {
123
+ const el = await fixture<ManageBar>(html`
124
+ <manage-bar
125
+ .modalManager=${new ModalManager()}
126
+ .selectedItems=${[{ identifier: '1', title: 'Item 1' }]}
127
+ removeAllowed
128
+ ></manage-bar>
129
+ `);
130
+ await el.updateComplete;
131
+
132
+ const removeButton = el.shadowRoot?.querySelector(
133
+ '.ia-button.danger'
134
+ ) as HTMLButtonElement;
135
+ expect(removeButton).to.exist;
136
+
137
+ const showModalSpy = Sinon.spy(
138
+ el.modalManager as ModalManagerInterface,
139
+ 'showModal'
140
+ );
141
+
142
+ await el.updateComplete;
143
+ removeButton?.click();
144
+
145
+ console.log(showModalSpy.args[0][0].config.title?.values[0]);
146
+
147
+ expect(showModalSpy.callCount).to.equal(1);
148
+ expect(el.modalManager?.classList.contains('remove-items')).to.be;
149
+ expect(showModalSpy.args[0][0].config.title?.values[0]).to.equal(
150
+ msg('Are you sure you want to remove these items?')
151
+ );
152
+ expect(showModalSpy.args[0][0].customModalContent).to.exist;
153
+ });
131
154
  });
@@ -0,0 +1,82 @@
1
+ /* eslint-disable import/no-duplicates */
2
+ import { expect, fixture } from '@open-wc/testing';
3
+ import { html } from 'lit';
4
+ import Sinon from 'sinon';
5
+ import type { ManageableItem } from '../../src/models';
6
+ import type { RemoveItemsModalContent } from '../../src/manage/remove-items-modal-content';
7
+ import '../../src/manage/remove-items-modal-content';
8
+
9
+ describe('RemoveItemsModalContent', () => {
10
+ const items: ManageableItem[] = [
11
+ { identifier: '1', title: 'Item 1', date: '2022-01-01' },
12
+ { identifier: '2', title: 'Item 2', date: '2022-01-02' },
13
+ ];
14
+
15
+ it('renders basic component', async () => {
16
+ const el = await fixture<RemoveItemsModalContent>(html`
17
+ <remove-items-modal-content
18
+ .items=${items}
19
+ .message=${''}
20
+ ></remove-items-modal-content>
21
+ `);
22
+
23
+ expect(el.shadowRoot?.querySelector('ul')).to.exist;
24
+ expect(el.shadowRoot?.querySelector('.button-bar')).to.exist;
25
+ expect(el.shadowRoot?.querySelector('.remove-items-btn')).to.exist;
26
+ });
27
+
28
+ it('renders list of items', async () => {
29
+ const el = await fixture<RemoveItemsModalContent>(html`
30
+ <remove-items-modal-content
31
+ .items=${items}
32
+ .message=${''}
33
+ ></remove-items-modal-content>
34
+ `);
35
+
36
+ const listItems = el.shadowRoot?.querySelectorAll('li');
37
+ expect(listItems).to.have.lengthOf(2);
38
+
39
+ listItems?.forEach((item, index) => {
40
+ expect(item.querySelector('.item-title')?.textContent).to.equal(
41
+ items[index].title
42
+ );
43
+ expect(item.querySelector('.item-date')?.textContent).to.equal(
44
+ items[index].date
45
+ );
46
+ });
47
+ });
48
+
49
+ it('renders message', async () => {
50
+ const message = 'This is a test message';
51
+ const el = await fixture<RemoveItemsModalContent>(html`
52
+ <remove-items-modal-content
53
+ .items=${[]}
54
+ .message=${message}
55
+ ></remove-items-modal-content>
56
+ `);
57
+
58
+ expect(el.shadowRoot?.querySelector('.message')?.textContent).to.equal(
59
+ message
60
+ );
61
+ });
62
+
63
+ it('dispatches confirm event when remove items button is clicked', async () => {
64
+ const el = await fixture<RemoveItemsModalContent>(html`
65
+ <remove-items-modal-content
66
+ .items=${items}
67
+ .message=${''}
68
+ ></remove-items-modal-content>
69
+ `);
70
+
71
+ const spy = Sinon.spy();
72
+ el.addEventListener('confirm', spy);
73
+
74
+ const button = el.shadowRoot?.querySelector(
75
+ '.remove-items-btn'
76
+ ) as HTMLInputElement;
77
+ button?.click();
78
+
79
+ expect(spy.calledOnce).to.be.true;
80
+ expect(spy.args[0][0].detail.items).to.deep.equal(items);
81
+ });
82
+ });