${when(
this.showSelectAll,
@@ -92,12 +106,12 @@ export class ManageBar extends LitElement {
this.dispatchEvent(new CustomEvent('cancel'));
}
- private removeClicked(): void {
+ private removeItemsClicked(): void {
this.dispatchEvent(new CustomEvent('removeItems'));
}
- private itemsManagerClicked(): void {
- this.dispatchEvent(new CustomEvent('itemsManager'));
+ private manageItemsClicked(): void {
+ this.dispatchEvent(new CustomEvent('manageItems'));
}
private selectAllClicked(): void {
@@ -108,6 +122,88 @@ export class ManageBar extends LitElement {
this.dispatchEvent(new CustomEvent('unselectAll'));
}
+ /**
+ * Shows a modal dialog confirming the list of items to be removed
+ * @param items Which items to list in the modal
+ */
+ private showRemoveItemsModal(): void {
+ const customModalContent = html`
+
this.removeItemsClicked()}
+ >
+ `;
+
+ const config = new ModalConfig({
+ showProcessingIndicator: false,
+ processingImageMode: 'processing',
+ bodyColor: '#fff',
+ headerColor: '#194880',
+ showHeaderLogo: false,
+ closeOnBackdropClick: true,
+ title: html`${msg('Are you sure you want to remove these items?')}`,
+ });
+
+ this.modalManager?.classList.add('remove-items');
+ this.modalManager?.showModal({
+ config,
+ customModalContent,
+ userClosedModalCallback: () => {
+ this.modalManager?.classList.remove('remove-items');
+ },
+ });
+ }
+
+ /**
+ * Shows a modal dialog indicating that item removal is being processed
+ */
+ showRemoveItemsProcessingModal(): void {
+ const config = new ModalConfig({
+ showProcessingIndicator: true,
+ processingImageMode: 'processing',
+ bodyColor: '#fff',
+ headerColor: '#194880',
+ showHeaderLogo: false,
+ closeOnBackdropClick: true,
+ title: html`${msg('Removing selected items...')}`,
+ });
+
+ this.modalManager?.classList.add('remove-items');
+ this.modalManager?.showModal({
+ config,
+ userClosedModalCallback: () => {
+ this.modalManager?.classList.remove('remove-items');
+ },
+ });
+ }
+
+ /**
+ * Shows a modal dialog indicating that an error occurred while removing items
+ */
+ showRemoveItemsErrorModal(): void {
+ const config = new ModalConfig({
+ showProcessingIndicator: false,
+ processingImageMode: 'processing',
+ bodyColor: '#fff',
+ headerColor: '#691916',
+ showHeaderLogo: false,
+ closeOnBackdropClick: true,
+ title: html`${msg('Error: unable to remove items')}`,
+ message: html`${msg(
+ 'An error occurred while removing items. Please try again in a few minutes.'
+ )}`,
+ });
+
+ this.modalManager?.classList.add('remove-items');
+ this.modalManager?.showModal({
+ config,
+ userClosedModalCallback: () => {
+ this.modalManager?.classList.remove('remove-items');
+ },
+ });
+ }
+
static get styles(): CSSResultGroup {
return css`
${iaButtonStyle}
diff --git a/src/manage/remove-items-modal-content.ts b/src/manage/remove-items-modal-content.ts
new file mode 100644
index 000000000..8db1005de
--- /dev/null
+++ b/src/manage/remove-items-modal-content.ts
@@ -0,0 +1,102 @@
+import { LitElement, html, css, nothing, TemplateResult, CSSResult } from 'lit';
+import { customElement, property } from 'lit/decorators.js';
+import { msg } from '@lit/localize';
+import { map } from 'lit/directives/map.js';
+import type { ManageableItem } from '../models';
+
+@customElement('remove-items-modal-content')
+export class RemoveItemsModalContent extends LitElement {
+ @property({ type: Object }) items: ManageableItem[] = [];
+
+ @property({ type: String }) message?: string;
+
+ render(): TemplateResult {
+ return html`
+
+ ${map(
+ this.items,
+ ({ title, date }) => html`
+ -
+ ${title ?? '[untitled]'}
+ ${date ?? ''}
+
+ `
+ )}
+
+ ${this.message ? html`
${this.message}
` : nothing}
+
+
+
+ `;
+ }
+
+ private removeItemsBtnClicked(): void {
+ this.dispatchEvent(
+ new CustomEvent<{ items: ManageableItem[] }>('confirm', {
+ detail: {
+ items: this.items,
+ },
+ })
+ );
+ }
+
+ static get styles(): CSSResult {
+ return css`
+ ul {
+ margin: 0;
+ padding: 0 10px;
+ font-size: 1.4rem;
+ list-style-type: none;
+ max-height: min(400px, 40vh);
+ overflow-y: auto;
+ }
+
+ li {
+ display: flex;
+ justify-content: space-between;
+ padding: 2px 0;
+ }
+ li:not(:last-of-type) {
+ border-bottom: 1px solid rgb(232, 232, 232);
+ }
+
+ .item-title {
+ word-break: break-word;
+ }
+
+ .item-date {
+ white-space: nowrap;
+ }
+
+ .message {
+ font-size: 1.4rem;
+ padding: 5px 10px;
+ }
+
+ .button-bar {
+ display: flex;
+ justify-content: center;
+ margin: 10px 5px;
+ }
+
+ .remove-items-btn {
+ margin-bottom: 10px;
+ padding: 10px;
+ border: 1px solid var(--primaryErrorCTABorder, #d43f3a);
+ border-radius: 4px;
+ color: white;
+ background: var(--primaryErrorCTAFill, #d9534f);
+ appearance: none;
+ cursor: pointer;
+ }
+ .remove-items-btn:hover {
+ background: rgba(var(--primaryErrorCTAFillRGB, 229, 28, 38), 0.9);
+ }
+ .remove-items-btn:active {
+ background: rgba(var(--primaryErrorCTAFillRGB, 229, 28, 38), 0.7);
+ }
+ `;
+ }
+}
diff --git a/src/models.ts b/src/models.ts
index cd52bb961..51916012b 100644
--- a/src/models.ts
+++ b/src/models.ts
@@ -675,3 +675,13 @@ export const suppressedCollections: Record
= {
americana: true,
toronto: true,
};
+
+/**
+ * A record of manageable item
+ */
+export interface ManageableItem {
+ identifier: string;
+ title?: string;
+ dateStr?: string;
+ date?: string;
+}
diff --git a/test/manage/manage-bar.test.ts b/test/manage/manage-bar.test.ts
index 92b94dc7a..267283466 100644
--- a/test/manage/manage-bar.test.ts
+++ b/test/manage/manage-bar.test.ts
@@ -2,9 +2,14 @@
import { expect, fixture } from '@open-wc/testing';
import { html } from 'lit';
import Sinon from 'sinon';
-import type { ManageBar } from '../../src/manage/manage-bar';
-
import '../../src/manage/manage-bar';
+import {
+ ModalManager,
+ ModalManagerInterface,
+} from '@internetarchive/modal-manager';
+import '@internetarchive/modal-manager';
+import { msg } from '@lit/localize';
+import type { ManageBar } from '../../src/manage/manage-bar';
describe('Manage bar', () => {
it('renders basic component', async () => {
@@ -38,7 +43,7 @@ describe('Manage bar', () => {
it('render item manager button for /search/ page', async () => {
const el = await fixture(
- html``
+ html``
);
expect(el.shadowRoot?.querySelector('.ia-button.warning')).to.exist;
});
@@ -84,21 +89,6 @@ describe('Manage bar', () => {
expect(spy.callCount).to.equal(1);
});
- it('emits event when Remove Items button clicked', async () => {
- const spy = Sinon.spy();
- const el = await fixture(
- html``
- );
-
- const removeItemsBtn = el.shadowRoot?.querySelector(
- '.ia-button.danger'
- ) as HTMLButtonElement;
- expect(removeItemsBtn).to.exist;
-
- removeItemsBtn.click();
- expect(spy.callCount).to.equal(1);
- });
-
it('emits event when Select All button clicked', async () => {
const spy = Sinon.spy();
const el = await fixture(
@@ -128,4 +118,37 @@ describe('Manage bar', () => {
unselectAllBtn.click();
expect(spy.callCount).to.equal(1);
});
+
+ it('opens the remove items modal when showRemoveItemsModal is clicked', async () => {
+ const el = await fixture(html`
+
+ `);
+ await el.updateComplete;
+
+ const removeButton = el.shadowRoot?.querySelector(
+ '.ia-button.danger'
+ ) as HTMLButtonElement;
+ expect(removeButton).to.exist;
+
+ const showModalSpy = Sinon.spy(
+ el.modalManager as ModalManagerInterface,
+ 'showModal'
+ );
+
+ await el.updateComplete;
+ removeButton?.click();
+
+ console.log(showModalSpy.args[0][0].config.title?.values[0]);
+
+ expect(showModalSpy.callCount).to.equal(1);
+ expect(el.modalManager?.classList.contains('remove-items')).to.be;
+ expect(showModalSpy.args[0][0].config.title?.values[0]).to.equal(
+ msg('Are you sure you want to remove these items?')
+ );
+ expect(showModalSpy.args[0][0].customModalContent).to.exist;
+ });
});
diff --git a/test/manage/remove-items-modal-content.test.ts b/test/manage/remove-items-modal-content.test.ts
new file mode 100644
index 000000000..5cb53e93e
--- /dev/null
+++ b/test/manage/remove-items-modal-content.test.ts
@@ -0,0 +1,82 @@
+/* eslint-disable import/no-duplicates */
+import { expect, fixture } from '@open-wc/testing';
+import { html } from 'lit';
+import Sinon from 'sinon';
+import type { ManageableItem } from '../../src/models';
+import type { RemoveItemsModalContent } from '../../src/manage/remove-items-modal-content';
+import '../../src/manage/remove-items-modal-content';
+
+describe('RemoveItemsModalContent', () => {
+ const items: ManageableItem[] = [
+ { identifier: '1', title: 'Item 1', date: '2022-01-01' },
+ { identifier: '2', title: 'Item 2', date: '2022-01-02' },
+ ];
+
+ it('renders basic component', async () => {
+ const el = await fixture(html`
+
+ `);
+
+ expect(el.shadowRoot?.querySelector('ul')).to.exist;
+ expect(el.shadowRoot?.querySelector('.button-bar')).to.exist;
+ expect(el.shadowRoot?.querySelector('.remove-items-btn')).to.exist;
+ });
+
+ it('renders list of items', async () => {
+ const el = await fixture(html`
+
+ `);
+
+ const listItems = el.shadowRoot?.querySelectorAll('li');
+ expect(listItems).to.have.lengthOf(2);
+
+ listItems?.forEach((item, index) => {
+ expect(item.querySelector('.item-title')?.textContent).to.equal(
+ items[index].title
+ );
+ expect(item.querySelector('.item-date')?.textContent).to.equal(
+ items[index].date
+ );
+ });
+ });
+
+ it('renders message', async () => {
+ const message = 'This is a test message';
+ const el = await fixture(html`
+
+ `);
+
+ expect(el.shadowRoot?.querySelector('.message')?.textContent).to.equal(
+ message
+ );
+ });
+
+ it('dispatches confirm event when remove items button is clicked', async () => {
+ const el = await fixture(html`
+
+ `);
+
+ const spy = Sinon.spy();
+ el.addEventListener('confirm', spy);
+
+ const button = el.shadowRoot?.querySelector(
+ '.remove-items-btn'
+ ) as HTMLInputElement;
+ button?.click();
+
+ expect(spy.calledOnce).to.be.true;
+ expect(spy.args[0][0].detail.items).to.deep.equal(items);
+ });
+});