Skip to content

Commit

Permalink
feat: add confirmation when deleting collections and objects (#433)
Browse files Browse the repository at this point in the history
* feat: add confirmation when deleting collections

* feat: add confirmation when deleting objects

* Merge branch 'develop' into feat/add-confirmation-when-deleting-collections-objects

* fix: apply suggestions from code review
  • Loading branch information
Arne Vandoorslaer authored Aug 23, 2021
1 parent 2603632 commit bf46632
Show file tree
Hide file tree
Showing 8 changed files with 232 additions and 13 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Alert, FormActors } from '@netwerk-digitaal-erfgoed/solid-crs-components';
import { Alert, FormActors, PopupComponent } from '@netwerk-digitaal-erfgoed/solid-crs-components';
import { ArgumentError, Collection, CollectionMemoryStore, CollectionObject, CollectionObjectMemoryStore, ConsoleLogger, LoggerLevel, MemoryTranslator, SolidMockService } from '@netwerk-digitaal-erfgoed/solid-crs-core';
import { interpret, Interpreter } from 'xstate';
import { AppEvents, DismissAlertEvent } from '../../app.events';
Expand Down Expand Up @@ -135,7 +135,7 @@ describe('CollectionRootComponent', () => {

await component.updateComplete;

const button = window.document.body.getElementsByTagName('nde-collection-root')[0].shadowRoot.querySelector('.delete') as HTMLElement;
const button = window.document.body.getElementsByTagName('nde-collection-root')[0].shadowRoot.querySelector('.confirm-delete') as HTMLElement;
button.click();

expect(machine.send).toHaveBeenCalledTimes(1);
Expand All @@ -152,6 +152,30 @@ describe('CollectionRootComponent', () => {

});

// it('should call toggleDelete and show popup when cancel button is clicked', async () => {

// window.document.body.appendChild(component);
// await component.updateComplete;
// component.deletePopup.hidden = false;

// const button = window.document.body.getElementsByTagName('nde-object-root')[0].shadowRoot.querySelector('.cancel-delete') as HTMLElement;
// button.click();
// expect(component.deletePopup.hidden).toEqual(true);

// });

// it('should call toggleDelete and show popup when delete icon is clicked', async () => {

// window.document.body.appendChild(component);
// await component.updateComplete;
// component.deletePopup.hidden = false;

// const button = window.document.body.getElementsByTagName('nde-object-root')[0].shadowRoot.querySelector('.delete') as HTMLElement;
// button.click();
// expect(component.deletePopup.hidden).toEqual(true);

// });

it('should send event when collection title is clicked', async () => {

machine.onTransition(async(state) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { html, property, PropertyValues, internalProperty, unsafeCSS, css, TemplateResult, CSSResult } from 'lit-element';
import { html, property, PropertyValues, internalProperty, unsafeCSS, css, TemplateResult, CSSResult, query } from 'lit-element';
import { ArgumentError, Collection, CollectionObject, Logger, Translator } from '@netwerk-digitaal-erfgoed/solid-crs-core';
import { FormEvent, FormActors, FormSubmissionStates, FormEvents, Alert, FormRootStates, FormValidationStates, FormCleanlinessStates } from '@netwerk-digitaal-erfgoed/solid-crs-components';
import { FormEvent, FormActors, FormSubmissionStates, FormEvents, Alert, FormRootStates, FormValidationStates, FormCleanlinessStates, PopupComponent } from '@netwerk-digitaal-erfgoed/solid-crs-components';
import { map } from 'rxjs/operators';
import { from } from 'rxjs';
import { ActorRef, Interpreter, State } from 'xstate';
Expand Down Expand Up @@ -89,6 +89,12 @@ export class CollectionRootComponent extends RxLitElement {
@internalProperty()
isDirty? = false;

/**
* The popup component shown when the delete icon is clicked
*/
@query('nde-popup#delete-popup')
deletePopup: PopupComponent;

/**
* Hook called on at every update after connection to the DOM.
*/
Expand Down Expand Up @@ -175,6 +181,8 @@ export class CollectionRootComponent extends RxLitElement {
*/
render(): TemplateResult {

const toggleDelete = () => { this.deletePopup.toggle(); };

// Create an alert components for each alert.
const alerts = this.alerts?.map((alert) => html`<nde-alert .logger='${this.logger}' .translator='${this.translator}' .alert='${alert}' @dismiss="${this.handleDismiss}"></nde-alert>`);

Expand Down Expand Up @@ -208,7 +216,7 @@ export class CollectionRootComponent extends RxLitElement {
${ this.isDirty && this.isValid ? html`<div slot="actions"><button class="no-padding inverse save" @click="${() => this.formActor.send(FormEvents.FORM_SUBMITTED)}" ?disabled="${this.isSubmitting}">${unsafeSVG(Save)}</button></div>` : '' }
${ this.state?.matches(CollectionStates.EDITING) ? html`<div slot="actions"><button class="no-padding inverse cancel" @click="${() => this.actor.send(CollectionEvents.CANCELLED_EDIT)}">${unsafeSVG(Cross)}</button></div>` : '' }
<div slot="actions"><button class="no-padding inverse create" @click="${() => this.actor.send(CollectionEvents.CLICKED_CREATE_OBJECT)}">${unsafeSVG(Plus)}</button></div>
${this.showDelete ? html`<div slot="actions"><button class="no-padding inverse delete" @click="${() => this.actor.send(CollectionEvents.CLICKED_DELETE, { collection: this.collection })}">${unsafeSVG(Trash)}</button></div>` : '' }
${this.showDelete ? html`<div slot="actions"><button class="no-padding inverse delete" @click="${() => toggleDelete()}">${unsafeSVG(Trash)}</button></div>` : '' }
</nde-content-header>
<div class="content">
Expand Down Expand Up @@ -238,11 +246,36 @@ export class CollectionRootComponent extends RxLitElement {
}
`
}
<nde-popup dark id="delete-popup">
<div slot="content">
<p>${this.translator?.translate('nde.features.collections.root.delete.title')}</p>
<div>
<button class='primary confirm-delete' @click="${() => { this.actor.send(CollectionEvents.CLICKED_DELETE); toggleDelete(); }}">
<span>${this.translator?.translate('nde.features.collections.root.delete.confirm')}</span>
</button>
<button class='light cancel-delete' @click="${() => toggleDelete()}">
<span>${this.translator?.translate('nde.features.collections.root.delete.cancel')}</span>
</button>
</div>
</div>
</nde-popup>
</div>
` : html``;

}

constructor() {

super();

if(!customElements.get('nde-popup')) {

customElements.define('nde-popup', PopupComponent);

}

}

/**
* The styles associated with the component.
*/
Expand Down Expand Up @@ -338,6 +371,23 @@ export class CollectionRootComponent extends RxLitElement {
.description {
margin-top: var(--gap-tiny);
}
#delete-popup div[slot="content"] {
background-color: var(--colors-foreground-inverse);
align-items: center;
padding: var(--gap-large);
display: flex;
flex-direction: column;
justify-content: center;
}
#delete-popup div[slot="content"] div button{
margin: var(--gap-large) var(--gap-tiny) 0;
}
button.accent {
color: var(--colors-foreground-inverse);
}
button.light {
color:var(--colors-primary-dark);
}
`,
];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ describe('ObjectRootComponent', () => {

if(state.matches(ObjectStates.IDLE) && state.context?.object) {

const button = window.document.body.getElementsByTagName('nde-object-root')[0].shadowRoot.querySelector('.delete') as HTMLElement;
const button = window.document.body.getElementsByTagName('nde-object-root')[0].shadowRoot.querySelector('.confirm-delete') as HTMLElement;

if(button) {

Expand All @@ -167,6 +167,30 @@ describe('ObjectRootComponent', () => {

});

it('should call toggleDelete and show popup when cancel button is clicked', async () => {

window.document.body.appendChild(component);
await component.updateComplete;
component.deletePopup.hidden = false;

const button = window.document.body.getElementsByTagName('nde-object-root')[0].shadowRoot.querySelector('.cancel-delete') as HTMLElement;
button.click();
expect(component.deletePopup.hidden).toEqual(true);

});

it('should call toggleDelete and show popup when delete icon is clicked', async () => {

window.document.body.appendChild(component);
await component.updateComplete;
component.deletePopup.hidden = false;

const button = window.document.body.getElementsByTagName('nde-object-root')[0].shadowRoot.querySelector('.delete') as HTMLElement;
button.click();
expect(component.deletePopup.hidden).toEqual(true);

});

it('should send event when create is clicked', async () => {

machine.start();
Expand Down Expand Up @@ -197,18 +221,55 @@ describe('ObjectRootComponent', () => {

});

it('should only show save and cancel buttons when form is dirty', async () => {
it('should not show save and cancel buttons when form is not dirty', async () => {

machine.start();

window.document.body.appendChild(component);
await component.updateComplete;

const save = window.document.body.getElementsByTagName('nde-object-root')[0].shadowRoot.querySelector('.save') as HTMLElement;
const cancel = window.document.body.getElementsByTagName('nde-object-root')[0].shadowRoot.querySelector('.cancel') as HTMLElement;
const reset = window.document.body.getElementsByTagName('nde-object-root')[0].shadowRoot.querySelector('.reset') as HTMLElement;

expect(save).toBeFalsy();
expect(cancel).toBeFalsy();
expect(reset).toBeFalsy();

});

it('should show save and cancel buttons when form is dirty', async () => {

machine.start();
machine.send(ObjectEvents.SELECTED_OBJECT, { object: collection1 });

window.document.body.appendChild(component);
await component.updateComplete;

machine.onTransition(async(state) => {

if(state.matches(ObjectStates.IDLE) && state.context?.object) {

component.isDirty = true;
component.isValid = true;
await component.updateComplete;

const save = window.document.body.getElementsByTagName('nde-object-root')[0].shadowRoot.querySelector('.save') as HTMLElement;
const reset = window.document.body.getElementsByTagName('nde-object-root')[0].shadowRoot.querySelector('.reset') as HTMLElement;

expect(save).toBeTruthy();
expect(reset).toBeTruthy();

machine.send = jest.fn();
component.formActor.send = jest.fn();

save.click();
reset.click();

expect(machine.send).toHaveBeenCalledTimes(1);
expect(component.formActor.send).toHaveBeenCalledTimes(1);

}

});

});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { html, property, PropertyValues, internalProperty, unsafeCSS, css, TemplateResult, CSSResult, query } from 'lit-element';
import { ArgumentError, Collection, CollectionObject, Logger, Translator } from '@netwerk-digitaal-erfgoed/solid-crs-core';
import { FormSubmissionStates, FormEvents, Alert, FormRootStates, FormCleanlinessStates, FormValidationStates, FormUpdatedEvent, formMachine } from '@netwerk-digitaal-erfgoed/solid-crs-components';
import { FormSubmissionStates, FormEvents, Alert, FormRootStates, FormCleanlinessStates, FormValidationStates, FormUpdatedEvent, formMachine, PopupComponent } from '@netwerk-digitaal-erfgoed/solid-crs-components';
import { map } from 'rxjs/operators';
import { from } from 'rxjs';
import { ActorRef, interpret, Interpreter, InterpreterStatus, State, DoneInvokeEvent, doneInvoke } from 'xstate';
Expand Down Expand Up @@ -137,6 +137,12 @@ export class ObjectRootComponent extends RxLitElement {
@internalProperty()
components: ComponentMetadata[];

/**
* The popup component shown when the delete icon is clicked
*/
@query('nde-popup#delete-popup')
deletePopup: PopupComponent;

/**
* Hook called on first update after connection to the DOM.
*/
Expand Down Expand Up @@ -444,6 +450,8 @@ export class ObjectRootComponent extends RxLitElement {
*/
render(): TemplateResult {

const toggleDelete = () => { this.deletePopup.toggle(); };

const idle = this.state?.matches(ObjectStates.IDLE);

// Create an alert components for each alert.
Expand All @@ -469,7 +477,7 @@ export class ObjectRootComponent extends RxLitElement {
${ idle && this.isDirty && this.isValid ? html`<div slot="actions"><button class="no-padding inverse save" @click="${() => { if(this.isDirty && this.isValid) { this.formActor.send(FormEvents.FORM_SUBMITTED); } }}">${unsafeSVG(Save)}</button></div>` : '' }
${ idle && this.isDirty ? html`<div slot="actions"><button class="no-padding inverse reset" @click="${() => { if(this.isDirty) { this.actor.send(new ClickedResetEvent()); } }}">${unsafeSVG(Cross)}</button></div>` : '' }
<div slot="actions"><button class="no-padding inverse delete" @click="${() => this.actor.send(new ClickedDeleteObjectEvent(this.object))}">${unsafeSVG(Trash)}</button></div>
<div slot="actions"><button class="no-padding inverse delete" @click="${() => toggleDelete()}">${unsafeSVG(Trash)}</button></div>
</nde-content-header>
<div class="content-and-sidebar">
Expand Down Expand Up @@ -504,11 +512,36 @@ export class ObjectRootComponent extends RxLitElement {
` : html`no formcards`}
`
: html``}
<nde-popup dark id="delete-popup">
<div slot="content">
<p>${this.translator?.translate('nde.features.object.root.delete.title')}</p>
<div>
<button class='primary confirm-delete' @click="${() => { this.actor.send(new ClickedDeleteObjectEvent(this.object)); toggleDelete(); }}">
<span>${this.translator?.translate('nde.features.object.root.delete.confirm')}</span>
</button>
<button class='light cancel-delete' @click="${() => toggleDelete()}">
<span>${this.translator?.translate('nde.features.object.root.delete.cancel')}</span>
</button>
</div>
</div>
</nde-popup>
</div>`
: html``;

}

constructor() {

super();

if(!customElements.get('nde-popup')) {

customElements.define('nde-popup', PopupComponent);

}

}

static get styles(): CSSResult[] {

return [
Expand Down Expand Up @@ -572,6 +605,23 @@ export class ObjectRootComponent extends RxLitElement {
max-width: var(--gap-normal);
height: var(--gap-normal);
}
#delete-popup div[slot="content"] {
background-color: var(--colors-foreground-inverse);
align-items: center;
padding: var(--gap-large);
display: flex;
flex-direction: column;
justify-content: center;
}
#delete-popup div[slot="content"] div button{
margin: var(--gap-large) var(--gap-tiny) 0;
}
button.accent {
color: var(--colors-foreground-inverse);
}
button.light {
color:var(--colors-primary-dark);
}
`,
];

Expand Down
30 changes: 30 additions & 0 deletions packages/solid-crs-manage/lib/i8n/nl-NL.json
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,21 @@
"locale": "nl-NL",
"value": "Collectie aangemaakt"
},
{
"key": "nde.features.collections.root.delete.confirm",
"locale": "nl-NL",
"value": "Verwijderen"
},
{
"key": "nde.features.collections.root.delete.cancel",
"locale": "nl-NL",
"value": "Annuleren"
},
{
"key": "nde.features.collections.root.delete.title",
"locale": "nl-NL",
"value": "Ben je zeker dat je deze collectie wil verwijderen?"
},
{
"key": "nde.features.collections.root.title",
"locale": "nl-NL",
Expand Down Expand Up @@ -184,6 +199,21 @@
"locale": "nl-NL",
"value": "Collecties"
},
{
"key": "nde.features.object.root.delete.confirm",
"locale": "nl-NL",
"value": "Verwijderen"
},
{
"key": "nde.features.object.root.delete.cancel",
"locale": "nl-NL",
"value": "Annuleren"
},
{
"key": "nde.features.object.root.delete.title",
"locale": "nl-NL",
"value": "Ben je zeker dat je dit object wil verwijderen?"
},
{
"key": "nde.features.object.new-object-name",
"locale": "nl-NL",
Expand Down
2 changes: 1 addition & 1 deletion packages/solid-crs-manage/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -110,4 +110,4 @@
}
}
}
}
}
Loading

0 comments on commit bf46632

Please sign in to comment.