Skip to content

Commit

Permalink
Provide a notification system (#790)
Browse files Browse the repository at this point in the history
* Add QbicDialog and interfaces

* Add a NotificationDialog.java

* Adapt AddProjectDialog.java

* update EditProjectInformationDialog.java

* Adapt ProjectUserRemovalConfirmationNotification.java

* make VariableLevel.java serializable

* Move shortcut handling to QBiC dialog

* adapt ExperimentalGroupsDialog.java

* adapt ExperimentalVariablesDialog.java

* close variable dialog after confirm

* adapt EditExperimentDialog.java

* fix closing on confirm for experiment dialogs

* adapt ExistingSamplesPreventGroupEdit.java

* fix ProjectDetailsComponent.java

* fix ProjectInformationMain.java

* adapt MeasurementDeletionConfirmationNotification.java

* adapt measurement dialog

* adapt sample batch dialogs

* adapt dialogs

* fix code smells

* Add toast notifications

* Fix closing on confirmation

* remove unused method

* do not require close confirmation by default

* Use NotificationDialog instead of styled notification

* add todo

* Add duration configuration

* add duration

* Modify scope and default values

* Fix imports

* fix imports

* add level to toast properties

* adapt styling

* add missing comma

* add missing parameter

* refactor API

Co-authored-by: Sven F. <[email protected]>

* change toast construction

* Update Javadoc

* Improve javadoc

Co-authored-by: wow-such-code <[email protected]>

* Fix broken merge

* Remove cancel confirmation interfaces

* add batch label to event

* rename message key

* add notification

* switch to notificatino factory

* switch to notificatino factory

* fix imports

* fix constructors

* remove unused code

* remove unused method

* add javadoc

* remove MessageSourceToastFactory.java

* add more javadoc

* Change level to warning

Co-authored-by: wow-such-code <[email protected]>

* Add missing reset

Co-authored-by: wow-such-code <[email protected]>

* add missing reset

Co-authored-by: wow-such-code <[email protected]>

* fix closing in AddExperimentDialog.java

* Fix closing of EditExperimentDialog.java

* adjust EditProjectInformationDialog.java closing

* Fix constructors

* fire missing events

* use cancel confirmation on measurement upload dialog

* add ESC behaviour

* add missing message

* add cancel confirmation for experimental variables

* add ESC check for experimental variable editing and creation

* Add sample batch registration cancel confirmation

* fire missing events

* adapt to 5 seconds instead of 5000

---------

Co-authored-by: Sven F. <[email protected]>
Co-authored-by: wow-such-code <[email protected]>
  • Loading branch information
3 people committed Sep 5, 2024
1 parent 1376430 commit 0b11090
Show file tree
Hide file tree
Showing 53 changed files with 1,296 additions and 550 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import jakarta.persistence.Convert;
import jakarta.persistence.Embeddable;
import jakarta.persistence.Embedded;
import java.io.Serializable;
import java.util.Objects;
import java.util.StringJoiner;
import life.qbic.projectmanagement.domain.model.experiment.repository.jpa.VariableNameAttributeConverter;
Expand All @@ -19,7 +20,7 @@
*/
@Embeddable
@Access(AccessType.FIELD)
public class VariableLevel {
public class VariableLevel implements Serializable {

@Convert(converter = VariableNameAttributeConverter.class)
private VariableName variableName;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
@import "toggle-button.css";
@import "vaadin-custom.css";
@import "virtuallist.css";
@import "toast.css";

vaadin-avatar-group vaadin-avatar,
.user-avatar {
Expand Down
44 changes: 44 additions & 0 deletions user-interface/frontend/themes/datamanager/components/toast.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
.toast-notification::part(content) {
/*you can style the notification content here*/
}

.toast-notification.success-toast::part(content) {
background-color: var(--lumo-success-color-10pct);
/*you can style the notification content here*/
}

.toast-notification.info-toast::part(content) {
/*you can style the notification content here*/
background-color: var(--lumo-primary-color-10pct);
}

.toast-notification .toast-content {
/*this is the content of your notification excluding the close button*/
width: 100%;
}

.toast-notification .toast-content strong {
margin-left: .5ch;
margin-right: .5ch;
}

.toast-notification .close-button {
/*you can style the toast close button here*/
}

.toast-notification .routing-container {
display: grid;
grid-template-columns: minmax(min-content, max-content) auto minmax(min-content, max-content);
column-gap: var(--lumo-space-m);
}

.toast-notification .routing-content {
/*in case of routing this can style the content without the routing link*/
align-content: center;
}

.toast-notification .routing-link {
grid-column: 3;
align-content: center;
justify-content: center;
}
Binary file modified user-interface/src/main/bundles/dev.bundle
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ public IdentityService userRegistrationService(
return new IdentityService(userRepository);
}


@Bean
public NewPasswordInput newPasswordInput(IdentityService identityService) {
return new NewPassword(identityService);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
/**
* Translates an exception into a user-friendly message.
* <p>
* For ApplicationExceptions, messages can be defined in messages.properties, other exceptions will
* For ApplicationExceptions, messages can be defined in error-messages.properties, other exceptions will
* be mapped to the default message.
*
* @since 1.0.0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@
import static life.qbic.logging.service.LoggerFactory.logger;

import com.vaadin.flow.component.UI;
import com.vaadin.flow.component.html.Span;
import com.vaadin.flow.server.ErrorEvent;
import life.qbic.application.commons.ApplicationException;
import life.qbic.datamanager.exceptionhandling.ErrorMessageTranslationService.UserFriendlyErrorMessage;
import life.qbic.datamanager.views.notifications.ErrorMessage;
import life.qbic.datamanager.views.notifications.StyledNotification;
import life.qbic.datamanager.views.notifications.NotificationDialog;
import life.qbic.logging.api.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
Expand Down Expand Up @@ -84,9 +84,9 @@ private void displayUserFriendlyMessage(UI ui, ApplicationException exception) {
}

private void showErrorDialog(UserFriendlyErrorMessage userFriendlyError) {
ErrorMessage errorMessage = new ErrorMessage(userFriendlyError.title(),
userFriendlyError.message());
StyledNotification styledNotification = new StyledNotification(errorMessage);
styledNotification.open();
NotificationDialog.errorDialog()
.withTitle(userFriendlyError.title())
.withContent(new Span(userFriendlyError.message()))
.open();
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
package life.qbic.datamanager.views.account;

import com.vaadin.flow.component.html.Span;
import com.vaadin.flow.component.icon.Icon;
import com.vaadin.flow.component.icon.VaadinIcon;
import life.qbic.datamanager.views.notifications.NotificationDialog;
import life.qbic.datamanager.views.notifications.NotificationLevel;

/**
* Warns the user that the personal access token will be deleted and cannot be used
Expand All @@ -13,17 +12,13 @@
public class AccessTokenDeletionConfirmationNotification extends NotificationDialog {

public AccessTokenDeletionConfirmationNotification() {
customizeHeader();
content.add(new Span(
super(NotificationLevel.WARNING);
withTitle("Personal Access Token will be deleted");
withContent(new Span(
"Deleting this Personal Access Token will make it unusable. Proceed?"));
setCancelable(true);
setConfirmText("Confirm");
setConfirmText("Delete Token");
}

private void customizeHeader() {
Icon errorIcon = new Icon(VaadinIcon.WARNING);
errorIcon.setClassName("warning-icon");
setTitle("Personal Access Token will be deleted");
setHeaderIcon(errorIcon);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ public AddPersonalAccessTokenDialog() {
setConfirmButtonLabel("Generate");
tokenDescription.setLabel("Token Description");
tokenDescription.setPlaceholder("Please enter the description of its usage");
expirationDate.setItems(computeSelectableExpirationDates());
List<Duration> selectableExpirationDates = computeSelectableExpirationDates();
expirationDate.setItems(selectableExpirationDates);
expirationDate.setItemLabelGenerator(
item -> item.toDays() + " days");
expirationDate.setLabel("Expiration");
Expand All @@ -49,6 +50,7 @@ public AddPersonalAccessTokenDialog() {
expirationDate.setHelperText("The token will expire " + formattedDate);
});
expirationDate.addClassName("expiration-date");
expirationDate.setValue(selectableExpirationDates.get(0));
personalAccessTokenDTOBinder = new Binder<>(PersonalAccessTokenFrontendBean.class);
personalAccessTokenDTOBinder.forField(tokenDescription)
.asRequired("Please provide a token description")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
import life.qbic.datamanager.views.account.PersonalAccessTokenComponent.DeleteTokenEvent;
import life.qbic.datamanager.views.account.PersonalAccessTokenComponent.PersonalAccessTokenFrontendBean;
import life.qbic.datamanager.views.general.Main;
import life.qbic.datamanager.views.notifications.MessageSourceNotificationFactory;
import life.qbic.datamanager.views.notifications.Toast;
import life.qbic.identity.api.PersonalAccessToken;
import life.qbic.identity.api.PersonalAccessTokenService;
import life.qbic.identity.api.RawToken;
Expand Down Expand Up @@ -46,15 +48,19 @@ public class PersonalAccessTokenMain extends Main implements BeforeEnterObserver
private final PersonalAccessTokenComponent personalAccessTokenComponent;
private final PersonalAccessTokenService personalAccessTokenService;
private final AuthenticationToUserIdTranslationService userIdTranslator;
private final MessageSourceNotificationFactory messageSourceNotificationFactory;

public PersonalAccessTokenMain(PersonalAccessTokenService personalAccessTokenService,
PersonalAccessTokenComponent personalAccessTokenComponent,
AuthenticationToUserIdTranslationService userIdTranslator) {
AuthenticationToUserIdTranslationService userIdTranslator,
MessageSourceNotificationFactory messageSourceNotificationFactory) {
this.personalAccessTokenService = requireNonNull(personalAccessTokenService,
"personalAccessTokenService must not be null");
this.personalAccessTokenComponent = requireNonNull(personalAccessTokenComponent,
"personalAccessTokenComponent must not be null");
this.userIdTranslator = requireNonNull(userIdTranslator, "userIdTranslator must not be null");
this.messageSourceNotificationFactory = requireNonNull(messageSourceNotificationFactory,
"messageSourceToastFactory must not be null");

addClassName("personal-access-token");
add(personalAccessTokenComponent);
Expand Down Expand Up @@ -97,6 +103,9 @@ private void onAddTokenClicked(AddTokenEvent addTokenEvent) {
.tokenDescription(), event.personalAccessTokenDTO().expirationDate());
personalAccessTokenComponent.showCreatedToken(createdToken);
event.getSource().close();
Toast toast = messageSourceNotificationFactory.toast("personal-access-token.created.success",
new Object[]{event.personalAccessTokenDTO().tokenDescription()}, getLocale());
toast.open();
});
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package life.qbic.datamanager.views.general;

import static java.util.Objects.nonNull;

import com.vaadin.flow.component.Component;

public class ComponentFunctions {

private ComponentFunctions() {
}

/**
* Navigates up through the tree and checks whether the potential parent is an actual ancestor of
* the other component.
*
* @param potentialParent the component suspected to be a parent of the other component
* @param other the potential child component
* @return true if protential parent is a parent of the other component
*/
public static boolean isParentOf(Component potentialParent, Component other) {
var currentParent = other.getParent().orElse(null);
while (nonNull(currentParent)) {
if (currentParent.equals(potentialParent)) {
return true;
}
currentParent = currentParent.getParent().orElse(null);
}
return false;
}


}
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
package life.qbic.datamanager.views.general;

import com.vaadin.flow.component.ClickEvent;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.Key;
import com.vaadin.flow.component.Shortcuts;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.dialog.Dialog;
import com.vaadin.flow.server.Command;

/**
* <b>Dialog to create something</b>
Expand All @@ -17,27 +12,20 @@
*
* @since 1.0.0
*/
public abstract class DialogWindow extends Dialog {
public abstract class DialogWindow extends QbicDialog {

protected final Button confirmButton = new Button("Confirm");
protected final Button cancelButton = new Button("Cancel");

protected DialogWindow() {
this.addClassName("dialog-window");
confirmButton.addClassName("primary");
cancelButton.setThemeName("tertiary");
setCloseOnOutsideClick(false);
setCloseOnEsc(false);
getFooter().add(cancelButton, confirmButton);
cancelButton.addClickListener(this::onCancelClicked);

confirmButton.addClassName("primary");
confirmButton.addClickListener(this::onConfirmClicked);
}

protected void specifyCancelShortcuts(Command onCreationCanceled) {
setCloseOnOutsideClick(false);
setCloseOnEsc(false);
Shortcuts.addShortcutListener(this,
onCreationCanceled, Key.ESCAPE);
cancelButton.setThemeName("tertiary");
cancelButton.addClickListener(this::onCancelClicked);
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package life.qbic.datamanager.views.general;

import com.vaadin.flow.component.Key;
import com.vaadin.flow.component.ShortcutEventListener;
import com.vaadin.flow.component.ShortcutRegistration;
import com.vaadin.flow.component.Shortcuts;
import com.vaadin.flow.component.dialog.Dialog;
import com.vaadin.flow.server.Command;
import java.util.Objects;

/**
* A {@link Dialog} with additional functionality.
* <p>
*
* @see Dialog
* @since 1.4.0
*/
public class QbicDialog extends Dialog {

private ShortcutRegistration escShortcut;

public QbicDialog() {
setCloseOnOutsideClick(false);
setCloseOnEsc(false);
setEscAction(it -> this.close());
}

public void setEscAction(ShortcutEventListener listener) {
if (Objects.nonNull(escShortcut)) {
escShortcut.remove();
}
escShortcut = Shortcuts.addShortcutListener(this, listener, Key.ESCAPE);
}

public void setEscAction(Command command) {
if (Objects.nonNull(escShortcut)) {
escShortcut.remove();
}
escShortcut = Shortcuts.addShortcutListener(this, command, Key.ESCAPE);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ protected WizardDialogWindow() {
* @since 1.0.0
*/
protected void onFinishClicked(ClickEvent<Button> clickEvent) {
this.close();
close();
}

/**
Expand Down
Loading

0 comments on commit 0b11090

Please sign in to comment.