From 04d438923d24221ab8990a599efa596625441a99 Mon Sep 17 00:00:00 2001 From: anhefti Date: Thu, 7 Feb 2019 17:03:46 +0100 Subject: [PATCH] SEBSERV-27 #Insitution List and actions --- .../seb/sebserver/gbl/model/EntityName.java | 5 + .../model/institution/LmsSetupTestResult.java | 2 +- .../ethz/seb/sebserver/gbl/util/Result.java | 6 + .../seb/sebserver/gui/service/RWTUtils.java | 28 --- .../gui/service/page/PageContext.java | 14 +- .../gui/service/page/PageEventListener.java | 25 -- .../service/page/PageMessageException.java | 23 ++ .../gui/service/page/action/Action.java | 110 ++++++++ .../service/page/action/ActionDefinition.java | 6 +- .../gui/service/page/action/ActionPane.java | 28 ++- .../page/action/InstitutionActions.java | 44 +++- .../page/action/SafeActionExecution.java | 68 ----- .../service/page/activity/ActivitiesPane.java | 51 ++-- .../page/activity/ActivitySelection.java | 86 +++---- .../gui/service/page/content/Institution.java | 13 + .../service/page/content/InstitutionList.java | 60 +++-- .../gui/service/page/event/ActionEvent.java | 2 + .../page/event/ActionEventListener.java | 1 - .../page/event/ActionPublishEvent.java | 42 +--- .../event/ActionPublishEventListener.java | 2 - .../page/event/ActivitySelectionListener.java | 2 - .../page/event/LogoutEventListener.java | 2 - .../gui/service/page/event/PageEvent.java | 7 +- .../service/page/event/PageEventListener.java | 41 +++ .../sebserver/gui/service/page/form/Form.java | 234 ++++++++++++++++++ .../gui/service/page/form/FormBuilder.java | 188 ++++++++++++++ .../gui/service/page/form/FormHandle.java | 91 +++++++ .../service/page/form/PageFormService.java | 54 ++++ .../page/impl/ComposerServiceImpl.java | 11 +- .../service/page/impl/DefaultPageLayout.java | 13 +- .../service/page/impl/PageContextImpl.java | 51 +++- .../gui/service/page/impl/SEBLogin.java | 4 +- .../gui/service/page/impl/SEBMainPage.java | 14 +- .../page/validation/FieldValidationError.java | 39 +++ .../remote/webservice/api/FormBinding.java | 15 ++ .../remote/webservice/api/RestCall.java | 22 +- .../remote/webservice/api/RestCallError.java | 4 + .../remote/webservice/api/RestService.java | 4 - .../api/institution/GetInstitution.java | 37 +++ .../api/institution/NewInstitution.java | 37 +++ .../OAuth2AuthorizationContextHolder.java | 11 + .../gui/service/table/ColumnDefinition.java | 9 + .../gui/service/table/EntityTable.java | 45 +++- .../gui/service/table/TableBuilder.java | 15 +- .../gui/service/table/TableNavigator.java | 4 +- .../gui/service/widget/WidgetFactory.java | 46 +++- src/main/resources/messages.properties | 15 +- src/main/resources/static/images/delete.png | Bin 0 -> 186 bytes .../resources/static/images/editAction.png | Bin 0 -> 165 bytes .../resources/static/images/newAction.png | Bin 165 -> 121 bytes 50 files changed, 1296 insertions(+), 335 deletions(-) delete mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/service/RWTUtils.java delete mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/service/page/PageEventListener.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/service/page/PageMessageException.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/service/page/action/Action.java delete mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/service/page/action/SafeActionExecution.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/service/page/content/Institution.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/service/page/event/PageEventListener.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/service/page/form/Form.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/service/page/form/FormBuilder.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/service/page/form/FormHandle.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/service/page/form/PageFormService.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/service/page/validation/FieldValidationError.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/FormBinding.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/institution/GetInstitution.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/institution/NewInstitution.java create mode 100644 src/main/resources/static/images/delete.png create mode 100644 src/main/resources/static/images/editAction.png diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/EntityName.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/EntityName.java index c102ed8a..c7627777 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/model/EntityName.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/EntityName.java @@ -56,6 +56,11 @@ public class EntityName implements ModelIdAware, ModelNameAware { return this.modelId; } + @JsonIgnore + public EntityKey getEntityKey() { + return new EntityKey(getModelId(), getEntityType()); + } + @Override public int hashCode() { final int prime = 31; diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/institution/LmsSetupTestResult.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/institution/LmsSetupTestResult.java index 4b44b2ae..099353ce 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/model/institution/LmsSetupTestResult.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/institution/LmsSetupTestResult.java @@ -95,7 +95,7 @@ public final class LmsSetupTestResult { } public static final LmsSetupTestResult ofQuizRequestError(final String message) { - return new LmsSetupTestResult(false, null, null, message); + return new LmsSetupTestResult(false, Collections.emptyList(), null, message); } } diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/util/Result.java b/src/main/java/ch/ethz/seb/sebserver/gbl/util/Result.java index 501f0919..cfd57be6 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/util/Result.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/util/Result.java @@ -88,6 +88,12 @@ public final class Result { } } + public void handleError(final Consumer errorHandler) { + if (this.error != null) { + errorHandler.accept(this.error); + } + } + /** Use this to get the resulting value or (if null) to get a given other value * * @param other the other value to get if the computed value is null diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/RWTUtils.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/RWTUtils.java deleted file mode 100644 index 3dfbf2df..00000000 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/RWTUtils.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET) - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ - -package ch.ethz.seb.sebserver.gui.service; - -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; - -public final class RWTUtils { - - public static final String TEXT_NAME_H2 = "h2"; - - public static void clearComposite(final Composite parent) { - if (parent == null) { - return; - } - - for (final Control control : parent.getChildren()) { - control.dispose(); - } - } - -} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/PageContext.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/PageContext.java index ae24cf57..1f5214dc 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/PageContext.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/PageContext.java @@ -11,6 +11,9 @@ package ch.ethz.seb.sebserver.gui.service.page; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Shell; +import ch.ethz.seb.sebserver.gui.service.page.action.Action; +import ch.ethz.seb.sebserver.gui.service.page.action.ActionDefinition; +import ch.ethz.seb.sebserver.gui.service.page.activity.ActivitySelection; import ch.ethz.seb.sebserver.gui.service.page.event.PageEvent; public interface PageContext { @@ -30,7 +33,10 @@ public interface PageContext { public static final String ATTR_PAGE_TEMPLATE_COMPOSER_NAME = "ATTR_PAGE_TEMPLATE_COMPOSER_NAME"; - public static final String INSTITUTION_ID = "INSTITUTION_ID"; + public static final String ATTR_ENTITY_ID = "ENTITY_ID"; + public static final String ATTR_PARENT_ENTITY_ID = "PARENT_ENTITY_ID"; + public static final String ATTR_ENTITY_TYPE = "ENTITY_TYPE"; + public static final String ATTR_PARENT_ENTITY_TYPE = "PARENT_ENTITY_TYPE"; // public static final String USER_NAME = "USER_NAME"; // public static final String PASSWORD = "PASSWORD"; @@ -86,6 +92,8 @@ public interface PageContext { * @return this PageContext instance (builder pattern) */ PageContext withAttr(String key, String value); + PageContext withSelection(ActivitySelection selection); + String getAttribute(String name); String getAttribute(String name, String def); @@ -99,6 +107,8 @@ public interface PageContext { * @param event the concrete PageEvent instance */ void publishPageEvent(T event); + Action createAction(ActionDefinition actionDefinition); + /** Apply a confirm dialog with a specified confirm message and a callback code * block that will be executed on users OK selection. * @@ -125,4 +135,6 @@ public interface PageContext { T logoutOnError(Throwable error); + void publishPageMessage(PageMessageException pme); + } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/PageEventListener.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/PageEventListener.java deleted file mode 100644 index ab98a22f..00000000 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/PageEventListener.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET) - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ - -package ch.ethz.seb.sebserver.gui.service.page; - -import ch.ethz.seb.sebserver.gui.service.page.event.PageEvent; - -public interface PageEventListener { - - String LISTENER_ATTRIBUTE_KEY = "PageEventListener"; - - boolean match(Class eventType); - - default int priority() { - return 1; - } - - void notify(T event); - -} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/PageMessageException.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/PageMessageException.java new file mode 100644 index 00000000..7b175b7c --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/PageMessageException.java @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package ch.ethz.seb.sebserver.gui.service.page; + +public class PageMessageException extends RuntimeException { + + private static final long serialVersionUID = -6967378384991469166L; + + public PageMessageException(final String message, final Throwable cause) { + super(message, cause); + } + + public PageMessageException(final String message) { + super(message); + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/action/Action.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/action/Action.java new file mode 100644 index 00000000..250bb662 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/action/Action.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package ch.ethz.seb.sebserver.gui.service.page.action; + +import java.util.Set; +import java.util.function.Function; +import java.util.function.Supplier; + +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ch.ethz.seb.sebserver.gbl.util.Result; +import ch.ethz.seb.sebserver.gui.service.page.PageContext; +import ch.ethz.seb.sebserver.gui.service.page.PageMessageException; +import ch.ethz.seb.sebserver.gui.service.page.event.ActionEvent; +import ch.ethz.seb.sebserver.gui.service.page.event.ActionPublishEvent; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService; + +public class Action implements Runnable { + + private static final Logger log = LoggerFactory.getLogger(Action.class); + + public final ActionDefinition definition; + String confirmationMessage; + String successMessage; + boolean updateOnSelection; + + final RestService restService; + final PageContext pageContext; + Function> exec; + Supplier> selectionSupplier; + + public Action( + final ActionDefinition definition, + final PageContext pageContext, + final RestService restService) { + + this.definition = definition; + this.pageContext = pageContext; + this.restService = restService; + } + + @Override + public void run() { + if (StringUtils.isNotBlank(this.confirmationMessage)) { + this.pageContext.applyConfirmDialog( + this.confirmationMessage, + () -> exec()); + } else { + exec(); + } + } + + private void exec() { + try { + + this.exec.apply(this) + .map(value -> { + this.pageContext.publishPageEvent( + new ActionEvent(this.definition, value)); + return value; + }) + .getOrThrow(); + + } catch (final PageMessageException pme) { + Action.this.pageContext.publishPageMessage(pme); + } catch (final Throwable t) { + log.error("Failed to execute action: {}", Action.this, t); + Action.this.pageContext.notifyError("action.error.unexpected.message", t); + } + } + + public Action withExec(final Function> exec) { + this.exec = exec; + return this; + } + + public Action withSelectionSupplier(final Supplier> selectionSupplier) { + this.selectionSupplier = selectionSupplier; + return this; + } + + public Action withConfirm(final String confirmationMessage) { + this.confirmationMessage = confirmationMessage; + return this; + } + + public Action withSuccess(final String successMessage) { + this.successMessage = successMessage; + return this; + } + + public Action withUpdateOnSelection() { + this.updateOnSelection = true; + return this; + } + + public PageContext publish() { + this.pageContext.publishPageEvent(new ActionPublishEvent(this)); + return this.pageContext; + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/action/ActionDefinition.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/action/ActionDefinition.java index 8edf5310..ae4e915f 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/action/ActionDefinition.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/action/ActionDefinition.java @@ -13,10 +13,14 @@ import ch.ethz.seb.sebserver.gui.service.widget.WidgetFactory.IconButtonType; public enum ActionDefinition { INSTITUTION_NEW( - "actions.new.institution", + "sebserver.institution.action.new", IconButtonType.NEW_ACTION), INSTITUTION_MODIFY( + "sebserver.institution.action.modify", + IconButtonType.MODIFY_ACTION), + + INSTITUTION_SAVE( "actions.modify.institution", IconButtonType.SAVE_ACTION), diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/action/ActionPane.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/action/ActionPane.java index e2268001..0bf060da 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/action/ActionPane.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/action/ActionPane.java @@ -22,11 +22,12 @@ import org.springframework.stereotype.Component; import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey; import ch.ethz.seb.sebserver.gui.service.page.PageContext; -import ch.ethz.seb.sebserver.gui.service.page.PageEventListener; import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer; import ch.ethz.seb.sebserver.gui.service.page.event.ActionPublishEvent; import ch.ethz.seb.sebserver.gui.service.page.event.ActionPublishEventListener; +import ch.ethz.seb.sebserver.gui.service.page.event.PageEventListener; import ch.ethz.seb.sebserver.gui.service.widget.WidgetFactory; +import ch.ethz.seb.sebserver.gui.service.widget.WidgetFactory.CustomVariant; @Lazy @Component @@ -42,18 +43,21 @@ public class ActionPane implements TemplateComposer { } @Override - public void compose(final PageContext composerCtx) { + public void compose(final PageContext pageContext) { final Label label = this.widgetFactory.labelLocalized( - composerCtx.getParent(), - "h3", + pageContext.getParent(), + CustomVariant.TEXT_H2, new LocTextKey("sebserver.actionpane.title")); + final GridData titleLayout = new GridData(SWT.FILL, SWT.TOP, true, false); titleLayout.verticalIndent = 10; titleLayout.horizontalIndent = 10; label.setLayoutData(titleLayout); - final Tree actions = this.widgetFactory.treeLocalized(composerCtx.getParent(), SWT.SINGLE | SWT.FULL_SELECTION); + final Tree actions = this.widgetFactory.treeLocalized( + pageContext.getParent(), + SWT.SINGLE | SWT.FULL_SELECTION); actions.setData(RWT.CUSTOM_VARIANT, "actions"); final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, true); actions.setLayoutData(gridData); @@ -69,7 +73,7 @@ public class ActionPane implements TemplateComposer { actions.addListener(SWT.Selection, event -> { final TreeItem treeItem = (TreeItem) event.item; - final Runnable action = (Runnable) treeItem.getData(ACTION_EVENT_CALL_KEY); + final Action action = (Action) treeItem.getData(ACTION_EVENT_CALL_KEY); action.run(); if (!treeItem.isDisposed()) { @@ -82,12 +86,16 @@ public class ActionPane implements TemplateComposer { new ActionPublishEventListener() { @Override public void notify(final ActionPublishEvent event) { + final TreeItem actionItem = ActionPane.this.widgetFactory.treeItemLocalized( actions, - event.actionDefinition.name); - actionItem.setImage(event.actionDefinition.icon.getImage(composerCtx.getParent().getDisplay())); - actionItem.setData(ACTION_EVENT_CALL_KEY, - new SafeActionExecution(composerCtx, event, event.run)); + event.action.definition.name); + + actionItem.setImage(event.action.definition.icon.getImage( + pageContext.getParent().getDisplay())); + + actionItem.setData(ACTION_EVENT_CALL_KEY, event.action); + } }); diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/action/InstitutionActions.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/action/InstitutionActions.java index fe9b1f32..40efa674 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/action/InstitutionActions.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/action/InstitutionActions.java @@ -8,9 +8,47 @@ package ch.ethz.seb.sebserver.gui.service.page.action; -public interface InstitutionActions { +import static ch.ethz.seb.sebserver.gui.service.page.activity.ActivitySelection.Activity.INSTITUTION_NODE; -// /** Use this higher-order function to create a new Institution action Runnable. +import java.util.Collection; +import java.util.function.Function; + +import ch.ethz.seb.sebserver.gbl.model.EntityKey; +import ch.ethz.seb.sebserver.gbl.model.EntityType; +import ch.ethz.seb.sebserver.gbl.util.Result; +import ch.ethz.seb.sebserver.gui.service.page.PageMessageException; +import ch.ethz.seb.sebserver.gui.service.page.event.ActivitySelectionEvent; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.institution.NewInstitution; +import ch.ethz.seb.sebserver.gui.service.table.EntityTable; + +public final class InstitutionActions { + + public static Result newInstitution(final Action action) { + return action.restService + .getBuilder(NewInstitution.class) + .call(); + } + + public static Function> editInstitution(final EntityTable fromTable) { + return action -> { + final Collection selection = fromTable.getSelection(); + if (selection.isEmpty()) { + return Result.ofError(new PageMessageException("sebserver.institution.info.pleaseSelect")); + } + + final EntityKey entityKey = new EntityKey( + selection.iterator().next(), + EntityType.INSTITUTION); + action.pageContext.publishPageEvent(new ActivitySelectionEvent( + INSTITUTION_NODE + .createSelection() + .withEntity(entityKey))); + + return Result.of(entityKey); + }; + } + +// /** Use this higher-order function to create a new Institution action function. // * // * @return */ // static Runnable newInstitution(final PageContext composerCtx, final RestServices restServices) { @@ -23,7 +61,7 @@ public interface InstitutionActions { // }; // } // -// /** Use this higher-order function to create a delete Institution action Runnable. +// /** Use this higher-order function to create a delete Institution action function. // * // * @return */ // static Runnable deleteInstitution(final PageContext composerCtx, final RestServices restServices, diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/action/SafeActionExecution.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/action/SafeActionExecution.java deleted file mode 100644 index 02ee9a22..00000000 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/action/SafeActionExecution.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET) - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ - -package ch.ethz.seb.sebserver.gui.service.page.action; - -import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import ch.ethz.seb.sebserver.gui.service.page.PageContext; -import ch.ethz.seb.sebserver.gui.service.page.event.ActionPublishEvent; - -public class SafeActionExecution implements Runnable { - - private static final Logger log = LoggerFactory.getLogger(SafeActionExecution.class); - - private final PageContext pageContext; - private final ActionPublishEvent actionEvent; - private final Runnable actionExecution; - - public SafeActionExecution( - final PageContext pageContext, - final ActionPublishEvent actionEvent, - final Runnable actionExecution) { - - this.pageContext = pageContext; - this.actionEvent = actionEvent; - this.actionExecution = actionExecution; - } - - @Override - public void run() { - try { - if (StringUtils.isNotBlank(this.actionEvent.confirmationMessage)) { - this.pageContext.applyConfirmDialog( - this.actionEvent.confirmationMessage, - createConfirmationCallback()); - } else { - this.actionExecution.run(); - } - } catch (final Throwable t) { - log.error("Failed to execute action: {}", this.actionEvent, t); - this.pageContext.notifyError("action.error.unexpected.message", t); - } - } - - private final Runnable createConfirmationCallback() { - return new Runnable() { - - @Override - public void run() { - try { - SafeActionExecution.this.actionExecution.run(); - } catch (final Throwable t) { - log.error("Failed to execute action: {}", SafeActionExecution.this.actionEvent, t); - SafeActionExecution.this.pageContext.notifyError("action.error.unexpected.message", t); - } - - } - }; - } - -} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/activity/ActivitiesPane.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/activity/ActivitiesPane.java index ecae2c79..804be34b 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/activity/ActivitiesPane.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/activity/ActivitiesPane.java @@ -9,9 +9,7 @@ package ch.ethz.seb.sebserver.gui.service.page.activity; import java.util.Collection; -import java.util.Collections; import java.util.EnumMap; -import java.util.List; import java.util.Map; import org.eclipse.swt.SWT; @@ -28,18 +26,18 @@ import ch.ethz.seb.sebserver.gbl.model.user.UserInfo; import ch.ethz.seb.sebserver.gbl.model.user.UserRole; import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey; import ch.ethz.seb.sebserver.gui.service.page.PageContext; -import ch.ethz.seb.sebserver.gui.service.page.PageEventListener; import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer; import ch.ethz.seb.sebserver.gui.service.page.action.ActionDefinition; import ch.ethz.seb.sebserver.gui.service.page.activity.ActivitySelection.Activity; import ch.ethz.seb.sebserver.gui.service.page.event.ActionEvent; import ch.ethz.seb.sebserver.gui.service.page.event.ActionEventListener; import ch.ethz.seb.sebserver.gui.service.page.event.ActivitySelectionEvent; +import ch.ethz.seb.sebserver.gui.service.page.event.PageEventListener; import ch.ethz.seb.sebserver.gui.service.page.impl.MainPageState; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService; -import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.institution.GetInstitutionNames; import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.AuthorizationContextHolder; import ch.ethz.seb.sebserver.gui.service.widget.WidgetFactory; +import ch.ethz.seb.sebserver.gui.service.widget.WidgetFactory.CustomVariant; @Lazy @Component @@ -76,7 +74,7 @@ public class ActivitiesPane implements TemplateComposer { final Label activities = this.widgetFactory.labelLocalized( pageContext.getParent(), - "h3", + CustomVariant.TEXT_H2, new LocTextKey("sebserver.activitiespane.title")); final GridData activitiesGridData = new GridData(SWT.FILL, SWT.TOP, true, false); activitiesGridData.horizontalIndent = 20; @@ -88,10 +86,10 @@ public class ActivitiesPane implements TemplateComposer { navigationGridData.horizontalIndent = 10; navigation.setLayoutData(navigationGridData); - final List insitutionNames = this.restService - .getBuilder(GetInstitutionNames.class) - .call() - .get(pageContext::notifyError, () -> Collections.emptyList()); +// final List insitutionNames = this.restService +// .getBuilder(GetInstitutionNames.class) +// .call() +// .get(pageContext::notifyError, () -> Collections.emptyList()); if (userInfo.hasRole(UserRole.SEB_SERVER_ADMIN)) { // institutions (list) as root @@ -100,12 +98,17 @@ public class ActivitiesPane implements TemplateComposer { Activity.INSTITUTION_ROOT.title); ActivitySelection.inject(institutions, Activity.INSTITUTION_ROOT.createSelection()); - for (final EntityName inst : insitutionNames) { - createInstitutionItem(institutions, inst); - } +// for (final EntityName inst : insitutionNames) { +// createInstitutionItem(institutions, inst); +// } } else { - final EntityName inst = insitutionNames.iterator().next(); - createInstitutionItem(navigation, inst); + // institution node as none root + final TreeItem institutions = this.widgetFactory.treeItemLocalized( + navigation, + Activity.INSTITUTION_ROOT.title); + ActivitySelection.inject(institutions, Activity.INSTITUTION_NODE.createSelection()); +// final EntityName inst = insitutionNames.iterator().next(); +// createInstitutionItem(navigation, inst); } // final TreeItem user = this.widgetFactory.treeItemLocalized( @@ -222,21 +225,25 @@ public class ActivitiesPane implements TemplateComposer { } } - static TreeItem createInstitutionItem(final Tree parent, final EntityName idAndName) { + static TreeItem createInstitutionItem(final Tree parent, final EntityName entityName) { final TreeItem institution = new TreeItem(parent, SWT.NONE); - createInstitutionItem(idAndName, institution); + createInstitutionItem(entityName, institution); return institution; } - static TreeItem createInstitutionItem(final TreeItem parent, final EntityName idAndName) { + static TreeItem createInstitutionItem(final TreeItem parent, final EntityName entityName) { final TreeItem institution = new TreeItem(parent, SWT.NONE); - createInstitutionItem(idAndName, institution); + createInstitutionItem(entityName, institution); return institution; } - static void createInstitutionItem(final EntityName idAndName, final TreeItem institution) { - institution.setText(idAndName.name); - ActivitySelection.inject(institution, Activity.INSTITUTION_NODE.createSelection(idAndName)); + static void createInstitutionItem(final EntityName entityName, final TreeItem institution) { + institution.setText(entityName.name); + ActivitySelection.inject( + institution, + Activity.INSTITUTION_NODE + .createSelection() + .withEntity(entityName.getEntityKey())); } static final TreeItem findItemByActivity( @@ -250,7 +257,7 @@ public class ActivitiesPane implements TemplateComposer { for (final TreeItem item : items) { final ActivitySelection activitySelection = ActivitySelection.get(item); - final String id = activitySelection.getObjectIdentifier(); + final String id = activitySelection.getEntityId(); if (activitySelection != null && activitySelection.activity == activity && (id == null || (objectId != null && objectId.equals(id)))) { return item; diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/activity/ActivitySelection.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/activity/ActivitySelection.java index fa0d9b03..2a531c1e 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/activity/ActivitySelection.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/activity/ActivitySelection.java @@ -8,11 +8,14 @@ package ch.ethz.seb.sebserver.gui.service.page.activity; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; import java.util.function.Consumer; import org.eclipse.swt.widgets.TreeItem; -import ch.ethz.seb.sebserver.gbl.model.EntityName; +import ch.ethz.seb.sebserver.gbl.model.EntityKey; import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey; import ch.ethz.seb.sebserver.gui.service.page.PageContext.AttributeKeys; import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer; @@ -30,7 +33,7 @@ public class ActivitySelection { }; public enum Activity { - NONE(TODOTemplate.class, TODOTemplate.class, (String) null), + NONE(TODOTemplate.class, TODOTemplate.class), INSTITUTION_ROOT( InstitutionList.class, ActionPane.class, @@ -38,7 +41,7 @@ public class ActivitySelection { INSTITUTION_NODE( TODOTemplate.class, ActionPane.class, - AttributeKeys.INSTITUTION_ID), + new LocTextKey("sebserver.activities.inst")), // // USERS(UserAccountsForm.class, ActionPane.class), // @@ -55,7 +58,7 @@ public class ActivitySelection { public final LocTextKey title; public final Class contentPaneComposer; public final Class actionPaneComposer; - public final String objectIdentifierAttribute; + //public final String modelIdAttribute; private Activity( final Class objectPaneComposer, @@ -65,43 +68,54 @@ public class ActivitySelection { this.title = title; this.contentPaneComposer = objectPaneComposer; this.actionPaneComposer = selectionPaneComposer; - this.objectIdentifierAttribute = null; } private Activity( final Class objectPaneComposer, - final Class selectionPaneComposer, - final String objectIdentifierAttribute) { + final Class selectionPaneComposer) { this.title = null; this.contentPaneComposer = objectPaneComposer; this.actionPaneComposer = selectionPaneComposer; - this.objectIdentifierAttribute = objectIdentifierAttribute; } public final ActivitySelection createSelection() { return new ActivitySelection(this); } - - public final ActivitySelection createSelection(final EntityName entityName) { - return new ActivitySelection(this, entityName); - } } private static final String ATTR_ACTIVITY_SELECTION = "ACTIVITY_SELECTION"; public final Activity activity; - public final EntityName entityName; + final Map attributes; Consumer expandFunction = EMPTY_FUNCTION; ActivitySelection(final Activity activity) { - this(activity, null); + this.activity = activity; + this.attributes = new HashMap<>(); } - ActivitySelection(final Activity activity, final EntityName entityName) { - this.activity = activity; - this.entityName = entityName; - this.expandFunction = EMPTY_FUNCTION; + public ActivitySelection withEntity(final EntityKey entityKey) { + if (entityKey != null) { + this.attributes.put(AttributeKeys.ATTR_ENTITY_ID, entityKey.modelId); + this.attributes.put(AttributeKeys.ATTR_ENTITY_TYPE, entityKey.entityType.name()); + } + + return this; + + } + + public ActivitySelection withParentEntity(final EntityKey parentEntityKey) { + if (parentEntityKey != null) { + this.attributes.put(AttributeKeys.ATTR_PARENT_ENTITY_ID, parentEntityKey.modelId); + this.attributes.put(AttributeKeys.ATTR_PARENT_ENTITY_TYPE, parentEntityKey.entityType.name()); + } + + return this; + } + + public Map getAttributes() { + return Collections.unmodifiableMap(this.attributes); } public ActivitySelection withExpandFunction(final Consumer expandFunction) { @@ -112,44 +126,12 @@ public class ActivitySelection { return this; } - public String getObjectIdentifier() { - if (this.entityName == null) { - return null; - } - - return this.entityName.modelId; - } - public void processExpand(final TreeItem item) { this.expandFunction.accept(item); } - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((this.activity == null) ? 0 : this.activity.hashCode()); - result = prime * result + ((this.entityName == null) ? 0 : this.entityName.hashCode()); - return result; - } - - @Override - public boolean equals(final Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - final ActivitySelection other = (ActivitySelection) obj; - if (this.activity != other.activity) - return false; - if (this.entityName == null) { - if (other.entityName != null) - return false; - } else if (!this.entityName.equals(other.entityName)) - return false; - return true; + public String getEntityId() { + return this.attributes.get(AttributeKeys.ATTR_ENTITY_ID); } public static ActivitySelection get(final TreeItem item) { diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/content/Institution.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/content/Institution.java new file mode 100644 index 00000000..c9cac7a3 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/content/Institution.java @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package ch.ethz.seb.sebserver.gui.service.page.content; + +public class Institution { + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/content/InstitutionList.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/content/InstitutionList.java index 8c2dd599..c7b121b6 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/content/InstitutionList.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/content/InstitutionList.java @@ -16,13 +16,17 @@ import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; import ch.ethz.seb.sebserver.gbl.model.Domain; +import ch.ethz.seb.sebserver.gbl.model.institution.Institution; import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey; import ch.ethz.seb.sebserver.gui.service.page.PageContext; import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer; +import ch.ethz.seb.sebserver.gui.service.page.action.ActionDefinition; +import ch.ethz.seb.sebserver.gui.service.page.action.InstitutionActions; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.institution.GetInstitutions; import ch.ethz.seb.sebserver.gui.service.table.ColumnDefinition; +import ch.ethz.seb.sebserver.gui.service.table.EntityTable; import ch.ethz.seb.sebserver.gui.service.widget.WidgetFactory; @Lazy @@ -49,37 +53,39 @@ public class InstitutionList implements TemplateComposer { content.setLayout(contentLayout); content.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + // title this.widgetFactory.labelLocalizedTitle( content, new LocTextKey("sebserver.institution.list.title")); - this.widgetFactory.entityTableBuilder(this.restService.getRestCall(GetInstitutions.class)) - .withPaging(3) - .withColumn(new ColumnDefinition<>( - Domain.INSTITUTION.ATTR_NAME, - new LocTextKey("sebserver.institution.list.column.name"), - null, - 0, - entity -> entity.name, - null, - true)) - .withColumn(new ColumnDefinition<>( - Domain.INSTITUTION.ATTR_URL_SUFFIX, - new LocTextKey("sebserver.institution.list.column.urlSuffix"), - null, - 0, - entity -> entity.urlSuffix, - null, - true)) - .withColumn(new ColumnDefinition<>( - Domain.INSTITUTION.ATTR_ACTIVE, - new LocTextKey("sebserver.institution.list.column.active"), - null, - 0, - entity -> entity.active, - null, - true)) - .compose(content); + // table + final EntityTable table = + this.widgetFactory.entityTableBuilder(this.restService.getRestCall(GetInstitutions.class)) + .withPaging(3) + .withColumn(new ColumnDefinition<>( + Domain.INSTITUTION.ATTR_NAME, + new LocTextKey("sebserver.institution.list.column.name"), + entity -> entity.name, + true)) + .withColumn(new ColumnDefinition<>( + Domain.INSTITUTION.ATTR_URL_SUFFIX, + new LocTextKey("sebserver.institution.list.column.urlSuffix"), + entity -> entity.urlSuffix, + true)) + .withColumn(new ColumnDefinition<>( + Domain.INSTITUTION.ATTR_ACTIVE, + new LocTextKey("sebserver.institution.list.column.active"), + entity -> entity.active, + true)) + .compose(content); + + // propagate content actions to action-pane + pageContext.createAction(ActionDefinition.INSTITUTION_NEW) + .withExec(InstitutionActions::newInstitution) + .publish() + .createAction(ActionDefinition.INSTITUTION_MODIFY) + .withExec(InstitutionActions.editInstitution(table)) + .publish(); } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/event/ActionEvent.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/event/ActionEvent.java index fc4f344b..41347606 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/event/ActionEvent.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/event/ActionEvent.java @@ -10,6 +10,8 @@ package ch.ethz.seb.sebserver.gui.service.page.event; import ch.ethz.seb.sebserver.gui.service.page.action.ActionDefinition; +/** This Event is used to propagate a user-action to the GUI system. + * Potentially every component can listen to an Event and react on the user-action */ public final class ActionEvent implements PageEvent { public final ActionDefinition actionDefinition; diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/event/ActionEventListener.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/event/ActionEventListener.java index 86e765a2..14f302e2 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/event/ActionEventListener.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/event/ActionEventListener.java @@ -13,7 +13,6 @@ import java.util.function.Predicate; import org.eclipse.swt.widgets.Widget; -import ch.ethz.seb.sebserver.gui.service.page.PageEventListener; import ch.ethz.seb.sebserver.gui.service.page.action.ActionDefinition; public interface ActionEventListener extends PageEventListener { diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/event/ActionPublishEvent.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/event/ActionPublishEvent.java index 08a8892b..57fb8ea8 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/event/ActionPublishEvent.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/event/ActionPublishEvent.java @@ -8,46 +8,16 @@ package ch.ethz.seb.sebserver.gui.service.page.event; -import ch.ethz.seb.sebserver.gui.service.page.action.ActionDefinition; +import ch.ethz.seb.sebserver.gui.service.page.action.Action; +/** This action is used to publish an Action to the Action-Pane for a specified context. + * The ActionPane is listening to this events and render specified actions on notify */ public class ActionPublishEvent implements PageEvent { - public final ActionDefinition actionDefinition; - public final Runnable run; - public final String confirmationMessage; - public final String successMessage; + public final Action action; - public ActionPublishEvent( - final ActionDefinition actionDefinition, - final Runnable run) { - - this(actionDefinition, run, null, null); - } - - public ActionPublishEvent( - final ActionDefinition actionDefinition, - final Runnable run, - final String confirmationMessage) { - - this(actionDefinition, run, confirmationMessage, null); - } - - public ActionPublishEvent( - final ActionDefinition actionDefinition, - final Runnable run, - final String confirmationMessage, - final String successMessage) { - - this.actionDefinition = actionDefinition; - this.run = run; - this.confirmationMessage = confirmationMessage; - this.successMessage = successMessage; - } - - @Override - public String toString() { - return "ActionPublishEvent [actionDefinition=" + this.actionDefinition + ", confirmationMessage=" - + this.confirmationMessage + ", successMessage=" + this.successMessage + "]"; + public ActionPublishEvent(final Action action) { + this.action = action; } } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/event/ActionPublishEventListener.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/event/ActionPublishEventListener.java index 0748fad6..69767104 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/event/ActionPublishEventListener.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/event/ActionPublishEventListener.java @@ -8,8 +8,6 @@ package ch.ethz.seb.sebserver.gui.service.page.event; -import ch.ethz.seb.sebserver.gui.service.page.PageEventListener; - public interface ActionPublishEventListener extends PageEventListener { @Override diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/event/ActivitySelectionListener.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/event/ActivitySelectionListener.java index 7a1942d9..c637ded2 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/event/ActivitySelectionListener.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/event/ActivitySelectionListener.java @@ -8,8 +8,6 @@ package ch.ethz.seb.sebserver.gui.service.page.event; -import ch.ethz.seb.sebserver.gui.service.page.PageEventListener; - public interface ActivitySelectionListener extends PageEventListener { @Override diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/event/LogoutEventListener.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/event/LogoutEventListener.java index f6839c32..cf4c83fe 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/event/LogoutEventListener.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/event/LogoutEventListener.java @@ -8,8 +8,6 @@ package ch.ethz.seb.sebserver.gui.service.page.event; -import ch.ethz.seb.sebserver.gui.service.page.PageEventListener; - public interface LogoutEventListener extends PageEventListener { @Override diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/event/PageEvent.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/event/PageEvent.java index 727888c7..12c3224a 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/event/PageEvent.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/event/PageEvent.java @@ -8,6 +8,11 @@ package ch.ethz.seb.sebserver.gui.service.page.event; +/** This is just a marker interface for all page events. + * Page events can be published to the actual page tree by using PageContext.publishPageEvent + * + * Potentially every component on the actual page tree can listen to an certain page event + * by adding a specified listener within the components setData functionality. + * see PageListener for more information */ public interface PageEvent { - } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/event/PageEventListener.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/event/PageEventListener.java new file mode 100644 index 00000000..785377ff --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/event/PageEventListener.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package ch.ethz.seb.sebserver.gui.service.page.event; + +/** Defines a listener for PageEvent. + * + * Potentially every component on the actual page tree can listen to an certain page event + * by adding a specified listener within the components setData functionality. + * + * @param the type of the PageEvent to listen to */ +public interface PageEventListener { + + /** The key name used to register PageEventListener instances within the + * setData functionality of a component */ + String LISTENER_ATTRIBUTE_KEY = "PageEventListener"; + + /** Used to check a concrete listener is interested in a specified type of PageEvent. + * + * @param eventType the PageEvent type + * @return whether the listener is interested in being notified by the event or not */ + boolean match(Class eventType); + + /** The listeners priority. + * Use this if a dedicated order or sequence of listener notification is needed. + * Default priority is 1 + * + * @return the priority of the listener that defines in witch sequence listeners of the same + * type get notified on a PageEvent propagation process on the current page-tree */ + default int priority() { + return 1; + } + + void notify(T event); + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/form/Form.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/form/Form.java new file mode 100644 index 00000000..db5ba337 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/form/Form.java @@ -0,0 +1,234 @@ +/* + * Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package ch.ethz.seb.sebserver.gui.service.page.form; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Consumer; +import java.util.function.Predicate; + +import org.eclipse.rap.rwt.RWT; +import org.eclipse.swt.widgets.Combo; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Text; + +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; + +import ch.ethz.seb.sebserver.gbl.api.JSONMapper; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.FormBinding; +import ch.ethz.seb.sebserver.gui.service.widget.SingleSelection; + +public final class Form implements FormBinding { + + private final JSONMapper jsonMapper; + private final ObjectNode objectRoot; + + private final Map> formFields = new LinkedHashMap<>(); + private final Map subForms = new LinkedHashMap<>(); + private final Map> subLists = new LinkedHashMap<>(); + private final Map> groups = new LinkedHashMap<>(); + + Form(final JSONMapper jsonMapper) { + this.jsonMapper = jsonMapper; + this.objectRoot = this.jsonMapper.createObjectNode(); + } + + @Override + public String getFormAsJson() { + try { + flush(); + return this.jsonMapper.writeValueAsString(this.objectRoot); + } catch (final Exception e) { + throw new RuntimeException("Unexpected error while trying to create json form Form post: ", e); + } + } + + public String getValue(final String name) { + final FormFieldAccessor formFieldAccessor = this.formFields.get(name); + if (formFieldAccessor != null) { + return formFieldAccessor.getValue(); + } + + return null; + } + + public void putStatic(final String name, final String value) { + this.objectRoot.put(name, value); + } + + public void addToGroup(final String groupName, final String fieldName) { + if (this.formFields.containsKey(fieldName)) { + this.groups.computeIfAbsent(groupName, k -> new HashSet<>()) + .add(fieldName); + } + } + + public Form putField(final String name, final Label label, final Label field) { + this.formFields.put(name, createAccessor(label, field)); + return this; + } + + public Form putField(final String name, final Label label, final Text field) { + this.formFields.put(name, createAccessor(label, field)); + return this; + } + + public void putField(final String name, final Label label, final Combo field) { + if (field instanceof SingleSelection) { + this.formFields.put(name, createAccessor(label, (SingleSelection) field)); + } + } + + public void putSubForm(final String name, final Form form) { + this.subForms.put(name, form); + } + + public Form getSubForm(final String name) { + return this.subForms.get(name); + } + + public void addSubForm(final String arrayName, final Form form) { + final List
array = this.subLists.computeIfAbsent(arrayName, k -> new ArrayList<>()); + array.add(form); + } + + public Form getSubForm(final String arrayName, final int index) { + final List array = this.subLists.get(arrayName); + if (array == null) { + return null; + } + + return array.get(index); + } + + public void allVisible() { + process( + name -> true, + ffa -> ffa.setVisible(true)); + } + + public void setVisible(final boolean visible, final String group) { + if (!this.groups.containsKey(group)) { + return; + } + + final Set namesSet = this.groups.get(group); + process( + name -> namesSet.contains(name), + ffa -> ffa.setVisible(visible)); + } + + public void process( + final Predicate nameFilter, + final Consumer> processor) { + + this.formFields.entrySet() + .stream() + .filter(entity -> nameFilter.test(entity.getKey())) + .map(entity -> entity.getValue()) + .forEach(processor); + } + + public void flush() { + for (final Map.Entry> entry : this.formFields.entrySet()) { + final FormFieldAccessor accessor = entry.getValue(); + if (accessor.control.isVisible()) { + this.objectRoot.put(entry.getKey(), accessor.getValue()); + } + } + + for (final Map.Entry entry : this.subForms.entrySet()) { + final Form subForm = entry.getValue(); + subForm.flush(); + final ObjectNode objectNode = this.jsonMapper.createObjectNode(); + this.objectRoot.set(entry.getKey(), objectNode); + } + + for (final Map.Entry> entry : this.subLists.entrySet()) { + final List value = entry.getValue(); + final ArrayNode arrayNode = this.jsonMapper.createArrayNode(); + final int index = 0; + for (final Form arrayForm : value) { + arrayForm.flush(); + arrayNode.insert(index, arrayForm.objectRoot); + } + this.objectRoot.set(entry.getKey(), arrayNode); + } + } + + //@formatter:off + private FormFieldAccessor createAccessor(final Label label, final Label field) { + return new FormFieldAccessor<>(label, field) { + @Override public String getValue() { return field.getText(); } + @Override public void setValue(final String value) { field.setText(value); } + }; + } + private FormFieldAccessor createAccessor(final Label label, final Text text) { + return new FormFieldAccessor<>(label, text) { + @Override public String getValue() { return text.getText(); } + @Override public void setValue(final String value) { text.setText(value); } + }; + } + private FormFieldAccessor createAccessor( + final Label label, + final SingleSelection singleSelection) { + + return new FormFieldAccessor<>(label, singleSelection) { + @Override public String getValue() { return singleSelection.getSelectionValue(); } + @Override public void setValue(final String value) { singleSelection.select(value); } + }; + } + //@formatter:on + + public static abstract class FormFieldAccessor { + + public final Label label; + public final T control; + private boolean hasError; + + public FormFieldAccessor(final Label label, final T control) { + this.label = label; + this.control = control; + } + + public abstract String getValue(); + + public abstract void setValue(String value); + + public void setVisible(final boolean visible) { + this.label.setVisible(visible); + this.control.setVisible(visible); + } + + public void setError(final String errorTooltip) { + if (!this.hasError) { + this.control.setData(RWT.CUSTOM_VARIANT, "error"); + //this.control.setBackground(new Color(this.control.getDisplay(), 255, 0, 0, 50)); + this.control.setToolTipText(errorTooltip); + this.hasError = true; + } + } + + public void resetError() { + if (this.hasError) { + this.control.setData(RWT.CUSTOM_VARIANT, null); + //this.control.setBackground(new Color(this.control.getDisplay(), 0, 0, 0, 0)); + this.control.setToolTipText(null); + this.hasError = false; + } + } + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/form/FormBuilder.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/form/FormBuilder.java new file mode 100644 index 00000000..8a62427c --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/form/FormBuilder.java @@ -0,0 +1,188 @@ +/* + * Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package ch.ethz.seb.sebserver.gui.service.page.form; + +import java.util.List; +import java.util.function.Consumer; + +import org.apache.commons.lang3.StringUtils; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Combo; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.TabItem; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ch.ethz.seb.sebserver.gbl.api.JSONMapper; +import ch.ethz.seb.sebserver.gbl.util.Tuple; +import ch.ethz.seb.sebserver.gui.service.i18n.PolyglotPageService; +import ch.ethz.seb.sebserver.gui.service.page.PageContext; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall; +import ch.ethz.seb.sebserver.gui.service.widget.WidgetFactory; + +public class FormBuilder { + + private static final Logger log = LoggerFactory.getLogger(FormBuilder.class); + + private final WidgetFactory widgetFactory; + private final PolyglotPageService polyglotPageService; + public final PageContext pageContext; + public final Composite formParent; + public final Form form; + + private boolean readonly = false; + + FormBuilder( + final JSONMapper jsonMapper, + final WidgetFactory widgetFactory, + final PolyglotPageService polyglotPageService, + final PageContext pageContext, + final int rows) { + + this.widgetFactory = widgetFactory; + this.polyglotPageService = polyglotPageService; + this.pageContext = pageContext; + this.form = new Form(jsonMapper); + + this.formParent = new Composite(pageContext.getParent(), SWT.NONE); + final GridLayout layout = new GridLayout(rows, true); + layout.horizontalSpacing = 10; + layout.verticalSpacing = 10; + layout.marginLeft = 10; + layout.marginTop = 10; + this.formParent.setLayout(layout); + this.formParent.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + } + + public FormBuilder readonly(final boolean readonly) { + this.readonly = readonly; + return this; + } + + public FormBuilder setVisible(final boolean visible, final String group) { + this.form.setVisible(visible, group); + return this; + } + + public FormBuilder setControl(final TabItem instTab) { + instTab.setControl(this.formParent); + return this; + } + + public FormBuilder addEmptyCell() { + return addEmptyCell(1); + } + + public FormBuilder addEmptyCell(final int span) { + this.widgetFactory.formEmpty(this.formParent, span, 1); + return this; + } + + public FormBuilder putStaticValue(final String name, final String value) { + try { + this.form.putStatic(name, value); + } catch (final Exception e) { + log.error("Failed to put static field value to json object: ", e); + } + return this; + } + + public FormBuilder addTextField( + final String name, + final String label, + final String value) { + + return addTextField(name, label, value, 1, null); + } + + public FormBuilder addTextField( + final String name, + final String label, + final String value, + final int span) { + + return addTextField(name, label, value, span, null); + } + + public FormBuilder addTextField( + final String name, + final String label, + final String value, + final int span, + final String group) { + + final Label lab = this.widgetFactory.formLabelLocalized(this.formParent, label); + if (this.readonly) { + this.form.putField(name, lab, this.widgetFactory.formValueLabel(this.formParent, value, span)); + } else { + this.form.putField(name, lab, this.widgetFactory.formTextInput(this.formParent, value, span, 1)); + } + if (StringUtils.isNoneBlank(group)) { + this.form.addToGroup(group, name); + } + return this; + } + + public FormBuilder addSingleSelection( + final String name, + final String label, + final String value, + final List> items, + final Consumer selectionListener) { + + return addSingleSelection(name, label, value, items, selectionListener, 1, null); + } + + public FormBuilder addSingleSelection( + final String name, + final String label, + final String value, + final List> items, + final Consumer selectionListener, + final int span) { + + return addSingleSelection(name, label, value, items, selectionListener, span, null); + } + + public FormBuilder addSingleSelection( + final String name, + final String label, + final String value, + final List> items, + final Consumer selectionListener, + final int span, + final String group) { + + final Label lab = this.widgetFactory.formLabelLocalized(this.formParent, label); + if (this.readonly) { + this.form.putField(name, lab, this.widgetFactory.formValueLabel(this.formParent, value, 2)); + } else { + final Combo selection = + this.widgetFactory.formSingleSelectionLocalized(this.formParent, value, items, span, 1); + this.form.putField(name, lab, selection); + if (selectionListener != null) { + selection.addListener(SWT.Selection, e -> { + selectionListener.accept(this.form); + }); + } + } + if (StringUtils.isNoneBlank(group)) { + this.form.addToGroup(group, name); + } + return this; + } + + public FormHandle buildFor(final RestCall post) { + return new FormHandle<>(this.pageContext, this.form, post, this.polyglotPageService.getI18nSupport()); + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/form/FormHandle.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/form/FormHandle.java new file mode 100644 index 00000000..9510507c --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/form/FormHandle.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package ch.ethz.seb.sebserver.gui.service.page.form; + +import java.util.function.Consumer; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ch.ethz.seb.sebserver.gui.service.i18n.I18nSupport; +import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey; +import ch.ethz.seb.sebserver.gui.service.page.PageContext; +import ch.ethz.seb.sebserver.gui.service.page.action.ActionDefinition; +import ch.ethz.seb.sebserver.gui.service.page.event.ActionEvent; +import ch.ethz.seb.sebserver.gui.service.page.form.Form.FormFieldAccessor; +import ch.ethz.seb.sebserver.gui.service.page.validation.FieldValidationError; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCallError; + +public class FormHandle { + + private static final Logger log = LoggerFactory.getLogger(FormHandle.class); + + public static final String FIELD_VALIDATION_LOCTEXT_PREFIX = "org.sebserver.form.validation.fieldError."; + + private final PageContext pageContext; + private final Form form; + private final RestCall post; + private final I18nSupport i18nSupport; + + FormHandle( + final PageContext pageContext, + final Form form, + final RestCall post, + final I18nSupport i18nSupport) { + + this.pageContext = pageContext; + this.form = form; + this.post = post; + this.i18nSupport = i18nSupport; + } + + public void doAPIPost(final ActionDefinition action) { + this.form.process( + name -> true, + fieldAccessor -> fieldAccessor.resetError()); + + this.post + .newBuilder() + .withFormBinding(this.form) + .call() + .map(result -> { + this.pageContext.publishPageEvent(new ActionEvent(action, result)); + return result; + }).onErrorDo(error -> { + if (error instanceof RestCallError) { + ((RestCallError) error) + .getErrorMessages() + .stream() + .map(FieldValidationError::new) + .forEach(fve -> this.form.process( + name -> name.equals(fve.fieldName), + fieldAccessor -> showValidationError(fieldAccessor, fve))); + } else { + log.error("Unexpected error while trying to post form: ", error); + this.pageContext.notifyError(error); + } + }); + } + + private final void showValidationError( + final FormFieldAccessor fieldAccessor, + final FieldValidationError valError) { + + fieldAccessor.setError(this.i18nSupport.getText(new LocTextKey( + FIELD_VALIDATION_LOCTEXT_PREFIX + valError.errorType, + (Object[]) valError.attributes))); + } + + public FormHandle process(final Consumer consumer) { + consumer.accept(this.form); + return this; + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/form/PageFormService.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/form/PageFormService.java new file mode 100644 index 00000000..e9d1c2dd --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/form/PageFormService.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package ch.ethz.seb.sebserver.gui.service.page.form; + +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; + +import ch.ethz.seb.sebserver.gbl.api.JSONMapper; +import ch.ethz.seb.sebserver.gui.service.i18n.PolyglotPageService; +import ch.ethz.seb.sebserver.gui.service.page.PageContext; +import ch.ethz.seb.sebserver.gui.service.widget.WidgetFactory; + +@Lazy +@Component +public class PageFormService { + + private final JSONMapper jsonMapper; + private final WidgetFactory widgetFactory; + private final PolyglotPageService polyglotPageService; + + public PageFormService( + final JSONMapper jsonMapper, + final WidgetFactory widgetFactory, + final PolyglotPageService polyglotPageService) { + + this.jsonMapper = jsonMapper; + this.widgetFactory = widgetFactory; + this.polyglotPageService = polyglotPageService; + } + + public FormBuilder getBuilder(final PageContext pageContext, final int rows) { + return new FormBuilder( + this.jsonMapper, + this.widgetFactory, + this.polyglotPageService, + pageContext, + rows); + } + + public WidgetFactory getWidgetFactory() { + return this.widgetFactory; + } + + public PolyglotPageService getPolyglotPageService() { + return this.polyglotPageService; + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/ComposerServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/ComposerServiceImpl.java index 49b307c3..c3033bb8 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/ComposerServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/ComposerServiceImpl.java @@ -20,13 +20,14 @@ import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; -import ch.ethz.seb.sebserver.gui.service.RWTUtils; import ch.ethz.seb.sebserver.gui.service.i18n.I18nSupport; import ch.ethz.seb.sebserver.gui.service.page.ComposerService; import ch.ethz.seb.sebserver.gui.service.page.PageContext; import ch.ethz.seb.sebserver.gui.service.page.PageDefinition; import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService; import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.AuthorizationContextHolder; +import ch.ethz.seb.sebserver.gui.service.widget.WidgetFactory; @Lazy @Service @@ -40,17 +41,20 @@ public class ComposerServiceImpl implements ComposerService { private final Class mainPageType = DefaultMainPage.class; final AuthorizationContextHolder authorizationContextHolder; + private final RestService restService; private final I18nSupport i18nSupport; private final Map composer; private final Map pages; public ComposerServiceImpl( final AuthorizationContextHolder authorizationContextHolder, + final RestService restService, final I18nSupport i18nSupport, final Collection composer, final Collection pageDefinitions) { this.authorizationContextHolder = authorizationContextHolder; + this.restService = restService; this.i18nSupport = i18nSupport; this.composer = composer .stream() @@ -108,7 +112,7 @@ public class ComposerServiceImpl implements ComposerService { if (composer.validate(pageContext)) { - RWTUtils.clearComposite(pageContext.getParent()); + WidgetFactory.clearComposite(pageContext.getParent()); try { composer.compose(pageContext); @@ -169,8 +173,7 @@ public class ComposerServiceImpl implements ComposerService { } private PageContext createPageContext(final Composite root) { - return new PageContextImpl( - this.i18nSupport, this, root, root, null); + return new PageContextImpl(this.restService, this.i18nSupport, this, root, root, null); } } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/DefaultPageLayout.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/DefaultPageLayout.java index 4f684e4a..1380e163 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/DefaultPageLayout.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/DefaultPageLayout.java @@ -27,6 +27,7 @@ import ch.ethz.seb.sebserver.gui.service.page.PageContext.AttributeKeys; import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer; import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.AuthorizationContextHolder; import ch.ethz.seb.sebserver.gui.service.widget.WidgetFactory; +import ch.ethz.seb.sebserver.gui.service.widget.WidgetFactory.CustomVariant; @Lazy @Component @@ -243,11 +244,17 @@ public class DefaultPageLayout implements TemplateComposer { rowLayout.marginRight = 20; footerRight.setLayout(rowLayout); - this.widgetFactory.labelLocalized(footerLeft, "footer", new LocTextKey("sebserver.overall.imprint")); - this.widgetFactory.labelLocalized(footerLeft, "footer", new LocTextKey("sebserver.overall.about")); + this.widgetFactory.labelLocalized( + footerLeft, + CustomVariant.FOOTER, + new LocTextKey("sebserver.overall.imprint")); + this.widgetFactory.labelLocalized( + footerLeft, + CustomVariant.FOOTER, + new LocTextKey("sebserver.overall.about")); this.widgetFactory.labelLocalized( footerRight, - "footer", + CustomVariant.FOOTER, new LocTextKey("sebserver.overall.version", this.sebServerVersion)); } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/PageContextImpl.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/PageContextImpl.java index b3e0e6f2..b5f21e18 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/PageContextImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/PageContextImpl.java @@ -29,8 +29,13 @@ import ch.ethz.seb.sebserver.gui.service.i18n.I18nSupport; import ch.ethz.seb.sebserver.gui.service.page.ComposerService; import ch.ethz.seb.sebserver.gui.service.page.PageContext; import ch.ethz.seb.sebserver.gui.service.page.PageDefinition; -import ch.ethz.seb.sebserver.gui.service.page.PageEventListener; +import ch.ethz.seb.sebserver.gui.service.page.PageMessageException; +import ch.ethz.seb.sebserver.gui.service.page.action.Action; +import ch.ethz.seb.sebserver.gui.service.page.action.ActionDefinition; +import ch.ethz.seb.sebserver.gui.service.page.activity.ActivitySelection; import ch.ethz.seb.sebserver.gui.service.page.event.PageEvent; +import ch.ethz.seb.sebserver.gui.service.page.event.PageEventListener; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService; import ch.ethz.seb.sebserver.gui.service.widget.Message; public class PageContextImpl implements PageContext { @@ -39,6 +44,7 @@ public class PageContextImpl implements PageContext { private static final ListenerComparator LIST_COMPARATOR = new ListenerComparator(); + private final RestService restService; private final I18nSupport i18nSupport; private final ComposerService composerService; private final Composite root; @@ -46,12 +52,14 @@ public class PageContextImpl implements PageContext { private final Map attributes; PageContextImpl( + final RestService restService, final I18nSupport i18nSupport, final ComposerService composerService, final Composite root, final Composite parent, final Map attributes) { + this.restService = restService; this.i18nSupport = i18nSupport; this.composerService = composerService; this.root = root; @@ -86,6 +94,7 @@ public class PageContextImpl implements PageContext { @Override public PageContext copyOf(final Composite parent) { return new PageContextImpl( + this.restService, this.i18nSupport, this.composerService, this.root, @@ -99,6 +108,7 @@ public class PageContextImpl implements PageContext { attrs.putAll(this.attributes); attrs.putAll(((PageContextImpl) otherContext).attributes); return new PageContextImpl( + this.restService, this.i18nSupport, this.composerService, this.root, @@ -112,10 +122,31 @@ public class PageContextImpl implements PageContext { attrs.putAll(this.attributes); attrs.put(key, value); return new PageContextImpl( + this.restService, this.i18nSupport, this.composerService, this.root, - this.parent, attrs); + this.parent, + attrs); + } + + @Override + public PageContext withSelection(final ActivitySelection selection) { + if (selection == null) { + return this; + } + + final Map attrs = new HashMap<>(); + attrs.putAll(this.attributes); + attrs.putAll(selection.getAttributes()); + + return new PageContextImpl( + this.restService, + this.i18nSupport, + this.composerService, + this.root, + this.parent, + attrs); } @Override @@ -160,6 +191,11 @@ public class PageContextImpl implements PageContext { .forEach(listener -> listener.notify(event)); } + @Override + public Action createAction(final ActionDefinition actionDefinition) { + return new Action(actionDefinition, this, this.restService); + } + @Override @SuppressWarnings("serial") public void applyConfirmDialog(final String confirmMessage, final Runnable onOK) { @@ -212,6 +248,17 @@ public class PageContextImpl implements PageContext { forwardToPage(this.composerService.loginPage(), pageContext); } + @Override + public void publishPageMessage(final PageMessageException pme) { + final MessageBox messageBox = new Message( + getShell(), + this.i18nSupport.getText("sebserver.page.message"), + this.i18nSupport.getText(pme.getMessage()), + SWT.NONE); + messageBox.setMarkupEnabled(true); + messageBox.open(null); + } + @Override public void notifyError(final String errorMessage, final Throwable error) { if (error instanceof APIMessageError) { diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/SEBLogin.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/SEBLogin.java index 9c5c05c8..4468f90f 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/SEBLogin.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/SEBLogin.java @@ -60,8 +60,8 @@ public class SEBLogin implements TemplateComposer { if (pageContext.hasAttribute((AttributeKeys.LGOUT_SUCCESS))) { final MessageBox logoutSuccess = new Message( pageContext.getShell(), - this.i18nSupport.getText("org.sebserver.logout"), - this.i18nSupport.getText("org.sebserver.logout.success.message"), + this.i18nSupport.getText("sebserver.logout"), + this.i18nSupport.getText("sebserver.logout.success.message"), SWT.ICON_INFORMATION); logoutSuccess.open(null); } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/SEBMainPage.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/SEBMainPage.java index a11dfe51..8d3f59ef 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/SEBMainPage.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/SEBMainPage.java @@ -23,11 +23,11 @@ import org.springframework.stereotype.Component; import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey; import ch.ethz.seb.sebserver.gui.service.page.PageContext; -import ch.ethz.seb.sebserver.gui.service.page.PageEventListener; import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer; import ch.ethz.seb.sebserver.gui.service.page.activity.ActivitiesPane; import ch.ethz.seb.sebserver.gui.service.page.event.ActivitySelectionEvent; import ch.ethz.seb.sebserver.gui.service.page.event.ActivitySelectionListener; +import ch.ethz.seb.sebserver.gui.service.page.event.PageEventListener; import ch.ethz.seb.sebserver.gui.service.widget.WidgetFactory; import ch.ethz.seb.sebserver.gui.service.widget.WidgetFactory.IconButtonType; @@ -128,9 +128,9 @@ public class SEBMainPage implements TemplateComposer { public void notify(final ActivitySelectionEvent event) { pageContext.composerService().compose( event.selection.activity.contentPaneComposer, - pageContext.copyOf(contentObjects).withAttr( - event.selection.activity.objectIdentifierAttribute, - event.selection.getObjectIdentifier())); + pageContext + .copyOf(contentObjects) + .withSelection(event.selection)); } }); @@ -150,9 +150,9 @@ public class SEBMainPage implements TemplateComposer { public void notify(final ActivitySelectionEvent event) { pageContext.composerService().compose( event.selection.activity.actionPaneComposer, - pageContext.copyOf(actionPane).withAttr( - event.selection.activity.objectIdentifierAttribute, - event.selection.getObjectIdentifier())); + pageContext + .copyOf(actionPane) + .withSelection(event.selection)); } }); diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/validation/FieldValidationError.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/validation/FieldValidationError.java new file mode 100644 index 00000000..1163d0d8 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/validation/FieldValidationError.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package ch.ethz.seb.sebserver.gui.service.page.validation; + +import ch.ethz.seb.sebserver.gbl.api.APIMessage; + +public class FieldValidationError { + + public final String messageCode; + public final String domainName; + public final String fieldName; + public final String errorType; + public final String[] attributes; + + public FieldValidationError(final APIMessage apiMessage) { + this( + apiMessage.messageCode, + apiMessage.attributes.toArray(new String[apiMessage.attributes.size()])); + } + + public FieldValidationError( + final String messageCode, + final String[] attributes) { + + this.messageCode = messageCode; + this.attributes = attributes; + + this.domainName = (attributes != null && attributes.length > 0) ? attributes[0] : null; + this.fieldName = (attributes != null && attributes.length > 1) ? attributes[1] : null; + this.errorType = (attributes != null && attributes.length > 2) ? attributes[2] : null; + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/FormBinding.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/FormBinding.java new file mode 100644 index 00000000..5192e8d5 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/FormBinding.java @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package ch.ethz.seb.sebserver.gui.service.remote.webservice.api; + +public interface FormBinding { + + String getFormAsJson(); + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/RestCall.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/RestCall.java index cb3f84f0..2257b69f 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/RestCall.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/RestCall.java @@ -66,6 +66,9 @@ public abstract class RestCall { } protected Result exchange(final RestCallBuilder builder) { + + log.debug("Call webservice API on {} for {}", this.path, builder); + try { final ResponseEntity responseEntity = RestCall.this.restService .getWebserviceAPIRestTemplate() @@ -90,6 +93,11 @@ public abstract class RestCall { responseEntity.getBody(), new TypeReference>() { })); + + log.debug( + "Webservice answered with well defined error- or validation-failure-response: ", + restCallError); + return Result.ofError(restCallError); } @@ -102,7 +110,7 @@ public abstract class RestCall { new TypeReference>() { })); } catch (final Exception e) { - log.error("Unable to handle rest call error: ", e); + log.error("Unexpected error-response while webservice API call for: {}", builder, e); } return Result.ofError(restCallError); @@ -180,6 +188,11 @@ public abstract class RestCall { return this; } + public RestCallBuilder withFormBinding(final FormBinding formBinding) { + // TODO Auto-generated method stub + return this; + } + public RestCallBuilder onlyActive(final boolean active) { this.queryParams.put(Entity.FILTER_ATTR_ACTIVE, Arrays.asList(String.valueOf(active))); return this; @@ -204,6 +217,13 @@ public abstract class RestCall { } } + @Override + public String toString() { + return "RestCallBuilder [httpHeaders=" + this.httpHeaders + ", body=" + this.body + ", queryParams=" + + this.queryParams + + ", uriVariables=" + this.uriVariables + "]"; + } + } } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/RestCallError.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/RestCallError.java index ec64e933..aecc9e41 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/RestCallError.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/RestCallError.java @@ -37,4 +37,8 @@ public class RestCallError extends RuntimeException implements APIMessageError { return !this.errors.isEmpty(); } + @Override + public String toString() { + return "RestCallError [errors=" + this.errors + "]"; + } } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/RestService.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/RestService.java index ea37aa89..5d7243e8 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/RestService.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/RestService.java @@ -12,8 +12,6 @@ import java.util.Collection; import java.util.Map; import java.util.stream.Collectors; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; @@ -29,8 +27,6 @@ import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.WebserviceURISer @GuiProfile public class RestService { - private static final Logger log = LoggerFactory.getLogger(RestService.class); - private final AuthorizationContextHolder authorizationContextHolder; private final WebserviceURIService webserviceURIBuilderSupplier; private final Map> calls; diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/institution/GetInstitution.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/institution/GetInstitution.java new file mode 100644 index 00000000..aa30f269 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/institution/GetInstitution.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package ch.ethz.seb.sebserver.gui.service.remote.webservice.api.institution; + +import org.springframework.context.annotation.Lazy; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Component; + +import com.fasterxml.jackson.core.type.TypeReference; + +import ch.ethz.seb.sebserver.gbl.api.SEBServerRestEndpoints; +import ch.ethz.seb.sebserver.gbl.model.institution.Institution; +import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall; + +@Lazy +@Component +@GuiProfile +public class GetInstitution extends RestCall { + + protected GetInstitution() { + super( + new TypeReference() { + }, + HttpMethod.GET, + MediaType.APPLICATION_FORM_URLENCODED, + SEBServerRestEndpoints.ENDPOINT_INSTITUTION); + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/institution/NewInstitution.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/institution/NewInstitution.java new file mode 100644 index 00000000..a363468f --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/institution/NewInstitution.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package ch.ethz.seb.sebserver.gui.service.remote.webservice.api.institution; + +import org.springframework.context.annotation.Lazy; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Component; + +import com.fasterxml.jackson.core.type.TypeReference; + +import ch.ethz.seb.sebserver.gbl.api.SEBServerRestEndpoints; +import ch.ethz.seb.sebserver.gbl.model.institution.Institution; +import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall; + +@Lazy +@Component +@GuiProfile +public class NewInstitution extends RestCall { + + protected NewInstitution() { + super( + new TypeReference() { + }, + HttpMethod.POST, + MediaType.APPLICATION_FORM_URLENCODED, + SEBServerRestEndpoints.ENDPOINT_INSTITUTION); + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/auth/OAuth2AuthorizationContextHolder.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/auth/OAuth2AuthorizationContextHolder.java index ad3f865e..507aeb21 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/auth/OAuth2AuthorizationContextHolder.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/auth/OAuth2AuthorizationContextHolder.java @@ -8,6 +8,7 @@ package ch.ethz.seb.sebserver.gui.service.remote.webservice.auth; +import java.io.IOException; import java.net.URI; import java.util.Arrays; import java.util.Collections; @@ -25,9 +26,11 @@ import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.http.client.ClientHttpRequestFactory; +import org.springframework.http.client.ClientHttpResponse; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.oauth2.client.DefaultOAuth2ClientContext; import org.springframework.security.oauth2.client.OAuth2RestTemplate; +import org.springframework.security.oauth2.client.http.OAuth2ErrorHandler; import org.springframework.security.oauth2.client.resource.OAuth2AccessDeniedException; import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails; import org.springframework.security.oauth2.client.token.AccessTokenRequest; @@ -162,6 +165,14 @@ public class OAuth2AuthorizationContextHolder implements AuthorizationContextHol this.restTemplate = new DisposableOAuth2RestTemplate(this.resource); this.restTemplate.setRequestFactory(clientHttpRequestFactory); + this.restTemplate.setErrorHandler(new OAuth2ErrorHandler(this.resource) { + @Override + public boolean hasError(final ClientHttpResponse response) throws IOException { + final HttpStatus statusCode = HttpStatus.resolve(response.getRawStatusCode()); + return (statusCode != null && statusCode.series() == HttpStatus.Series.SERVER_ERROR); + } + + }); this.revokeTokenURI = webserviceURIService.getOAuthRevokeTokenURI(); this.currentUserURI = webserviceURIService.getCurrentUserRequestURI(); diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/table/ColumnDefinition.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/table/ColumnDefinition.java index 6684522e..fa9e4b88 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/table/ColumnDefinition.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/table/ColumnDefinition.java @@ -40,6 +40,15 @@ public final class ColumnDefinition { this(columnName, displayName, null, widthPercent, null, null, false); } + public ColumnDefinition( + final String columnName, + final LocTextKey displayName, + final Function valueSupplier, + final boolean sortable) { + + this(columnName, displayName, null, -1, valueSupplier, null, sortable); + } + public ColumnDefinition( final String columnName, final LocTextKey displayName, diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/table/EntityTable.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/table/EntityTable.java index e664473c..bfafa336 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/table/EntityTable.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/table/EntityTable.java @@ -8,7 +8,11 @@ package ch.ethz.seb.sebserver.gui.service.table; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; import java.util.List; +import java.util.stream.Collectors; import org.eclipse.swt.SWT; import org.eclipse.swt.layout.GridData; @@ -46,8 +50,6 @@ public class EntityTable extends Composite { private final Table table; private final TableNavigator navigator; - private final boolean selectableRows; - private int pageNumber = 1; private int pageSize; private String sortColumn = null; @@ -56,31 +58,34 @@ public class EntityTable extends Composite { private boolean columnsWithSameWidth = true; EntityTable( + final int type, final Composite parent, final RestCall> restCall, final WidgetFactory widgetFactory, final List> columns, final List actions, final int pageSize, - final boolean withFilter, - final boolean selectableRows) { + final boolean withFilter) { - super(parent, SWT.NONE); + super(parent, type); this.widgetFactory = widgetFactory; this.restCall = restCall; this.columns = Utils.immutableListOf(columns); this.actions = Utils.immutableListOf(actions); super.setLayout(new GridLayout()); - super.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false)); + GridData gridData = new GridData(SWT.FILL, SWT.TOP, true, false); + + gridData.heightHint = (pageSize + 1) * 40; + super.setLayoutData(gridData); this.pageSize = pageSize; this.filter = (withFilter) ? new TableFilter<>(this) : null; - this.selectableRows = selectableRows; this.table = widgetFactory.tableLocalized(this); this.table.setLayout(new GridLayout(columns.size(), true)); - final GridData gridData = new GridData(SWT.FILL, SWT.CENTER, true, false); + gridData = new GridData(SWT.FILL, SWT.CENTER, true, false); + gridData.heightHint = (pageSize + 1) * 25; this.table.setLayoutData(gridData); this.table.addListener(SWT.Resize, this::adaptColumnWidth); @@ -148,6 +153,18 @@ public class EntityTable extends Composite { this.sortOrder); } + public Collection getSelection() { + final TableItem[] selection = this.table.getSelection(); + if (selection == null) { + return Collections.emptyList(); + } + + return Arrays.asList(selection) + .stream() + .map(this::getRowDataId) + .collect(Collectors.toList()); + } + private void createTableColumns() { for (final ColumnDefinition column : this.columns) { final TableColumn tableColumn = this.widgetFactory.tableColumnLocalized( @@ -204,9 +221,6 @@ public class EntityTable extends Composite { final TableItem item = new TableItem(this.table, SWT.NONE); item.setData(TABLE_ROW_DATA, row); int index = 0; - if (this.selectableRows) { - // TODO - } for (final ColumnDefinition column : this.columns) { final Object value = column.valueSupplier.apply(row); if (value instanceof Boolean) { @@ -247,4 +261,13 @@ public class EntityTable extends Composite { } } + @SuppressWarnings("unchecked") + private ROW getRowData(final TableItem item) { + return (ROW) item.getData(TABLE_ROW_DATA); + } + + private String getRowDataId(final TableItem item) { + return getRowData(item).getModelId(); + } + } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/table/TableBuilder.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/table/TableBuilder.java index 637c7540..34a0cd8a 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/table/TableBuilder.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/table/TableBuilder.java @@ -11,6 +11,7 @@ package ch.ethz.seb.sebserver.gui.service.table; import java.util.ArrayList; import java.util.List; +import org.eclipse.swt.SWT; import org.eclipse.swt.widgets.Composite; import ch.ethz.seb.sebserver.gbl.model.Entity; @@ -41,7 +42,7 @@ public class TableBuilder { final List actions = new ArrayList<>(); private int pageSize = -1; - private boolean selectableRows = false; + private int type = SWT.NONE; public TableBuilder( final WidgetFactory widgetFactory, @@ -61,13 +62,13 @@ public class TableBuilder { return this; } - public TableBuilder withSelectableRows() { - this.selectableRows = true; + public TableBuilder withAction(final TableRowAction action) { + this.actions.add(action); return this; } - public TableBuilder withAction(final TableRowAction action) { - this.actions.add(action); + public TableBuilder withMultiselection() { + this.type |= SWT.MULTI; return this; } @@ -79,14 +80,14 @@ public class TableBuilder { .isPresent(); return new EntityTable<>( + this.type, parent, this.restCall, this.widgetFactory, this.columns, this.actions, this.pageSize, - withFilter, - this.selectableRows); + withFilter); } } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/table/TableNavigator.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/table/TableNavigator.java index 72dbcc2a..b2e455b4 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/table/TableNavigator.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/table/TableNavigator.java @@ -17,7 +17,7 @@ import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Label; import ch.ethz.seb.sebserver.gbl.model.Page; -import ch.ethz.seb.sebserver.gui.service.RWTUtils; +import ch.ethz.seb.sebserver.gui.service.widget.WidgetFactory; public class TableNavigator extends Composite { @@ -39,7 +39,7 @@ public class TableNavigator extends Composite { public Page update(final Page pageData) { // clear all - RWTUtils.clearComposite(this); + WidgetFactory.clearComposite(this); final int pageNumber = pageData.getPageNumber(); final int numberOfPages = pageData.getNumberOfPages(); diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/widget/WidgetFactory.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/widget/WidgetFactory.java index f9de2839..c3194a71 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/widget/WidgetFactory.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/widget/WidgetFactory.java @@ -25,6 +25,7 @@ import org.eclipse.swt.layout.GridData; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Combo; import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Table; @@ -42,7 +43,6 @@ import ch.ethz.seb.sebserver.gbl.model.Entity; import ch.ethz.seb.sebserver.gbl.model.Page; import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; import ch.ethz.seb.sebserver.gbl.util.Tuple; -import ch.ethz.seb.sebserver.gui.service.RWTUtils; import ch.ethz.seb.sebserver.gui.service.i18n.I18nSupport; import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey; import ch.ethz.seb.sebserver.gui.service.i18n.PolyglotPageService; @@ -57,9 +57,27 @@ public class WidgetFactory { private static final Logger log = LoggerFactory.getLogger(WidgetFactory.class); + public enum CustomVariant { + TEXT_H1("h1"), + TEXT_H2("h2"), + TEXT_H3("h3"), + TEXT_ACTION("action"), + + FOOTER("footer"), + + ; + + public final String key; + + private CustomVariant(final String key) { + this.key = key; + } + } + public enum IconButtonType { MAXIMIZE("maximize.png"), MINIMIZE("minimize.png"), + MODIFY_ACTION("editAction.png"), SAVE_ACTION("saveAction.png"), NEW_ACTION("newAction.png"), DELETE_ACTION("deleteAction.png"), @@ -108,10 +126,10 @@ public class WidgetFactory { return button; } - public Button buttonLocalized(final Composite parent, final String style, final String locTextKey) { + public Button buttonLocalized(final Composite parent, final CustomVariant variant, final String locTextKey) { final Button button = new Button(parent, SWT.NONE); this.injectI18n(button, new LocTextKey(locTextKey)); - button.setData(RWT.CUSTOM_VARIANT, style); + button.setData(RWT.CUSTOM_VARIANT, variant.key); return button; } @@ -133,10 +151,10 @@ public class WidgetFactory { return label; } - public Label labelLocalized(final Composite parent, final String style, final LocTextKey locTextKey) { + public Label labelLocalized(final Composite parent, final CustomVariant variant, final LocTextKey locTextKey) { final Label label = new Label(parent, SWT.NONE); this.injectI18n(label, locTextKey); - label.setData(RWT.CUSTOM_VARIANT, style); + label.setData(RWT.CUSTOM_VARIANT, variant.key); return label; } @@ -152,24 +170,24 @@ public class WidgetFactory { public Label labelLocalized( final Composite parent, - final String style, + final CustomVariant variant, final LocTextKey locTextKey, final LocTextKey locToolTextKey) { final Label label = new Label(parent, SWT.NONE); this.injectI18n(label, locTextKey, locToolTextKey); - label.setData(RWT.CUSTOM_VARIANT, style); + label.setData(RWT.CUSTOM_VARIANT, variant.key); return label; } public Label labelLocalizedTitle(final Composite content, final LocTextKey locTextKey) { - final Label labelLocalized = labelLocalized(content, RWTUtils.TEXT_NAME_H2, locTextKey); + final Label labelLocalized = labelLocalized(content, CustomVariant.TEXT_H1, locTextKey); labelLocalized.setLayoutData(new GridData(SWT.TOP, SWT.LEFT, true, false)); return labelLocalized; } public Tree treeLocalized(final Composite parent, final int style) { - final Tree tree = new Tree(parent, SWT.SINGLE | SWT.FULL_SELECTION); + final Tree tree = new Tree(parent, style); this.injectI18n(tree); return tree; } @@ -381,6 +399,16 @@ public class WidgetFactory { } } + public static void clearComposite(final Composite parent) { + if (parent == null) { + return; + } + + for (final Control control : parent.getChildren()) { + control.dispose(); + } + } + private static Consumer treeFunction(final I18nSupport i18nSupport) { return tree -> updateLocale(tree.getItems(), i18nSupport); } diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties index e3a0e38c..7cec06b7 100644 --- a/src/main/resources/messages.properties +++ b/src/main/resources/messages.properties @@ -16,6 +16,8 @@ sebserver.login.pwd=Password sebserver.login.login=Sign In sebserver.login.failed.title=Login Failed sebserver.login.failed.message=Access Denied: Wrong username or password +sebserver.logout=Sign out +sebserver.logout.success.message=You have been successfully signed out. ################################ # Main Page @@ -29,6 +31,7 @@ sebserver.actionpane.title=Actions sebserver.activities.inst=Institution sebserver.error.unexpected=Unexpected Error +sebserver.page.message=Information ################################ @@ -38,4 +41,14 @@ sebserver.error.unexpected=Unexpected Error sebserver.institution.list.title=Institutions sebserver.institution.list.column.name=Name sebserver.institution.list.column.urlSuffix=URL Suffix -sebserver.institution.list.column.active=Active \ No newline at end of file +sebserver.institution.list.column.active=Active +sebserver.institution.action.new=New Institution +sebserver.institution.action.modify=Edit Institution +sebserver.institution.action.delete=Delete Institution +sebserver.institution.info.pleaseSelect=Please Select an Institution from the Table first. + +################################ +# Form validation +################################ + +sebserver.form.validation.fieldError.size=The size must be between {3} and {4} \ No newline at end of file diff --git a/src/main/resources/static/images/delete.png b/src/main/resources/static/images/delete.png new file mode 100644 index 0000000000000000000000000000000000000000..faddb41049cbaf709ae52aad9c546dbd1c7c5283 GIT binary patch literal 186 zcmeAS@N?(olHy`uVBq!ia0vp^LLkh+0wn(&ce?|m8a-VcLn;`P7YG_8-8f#r!13KM zBYARJ!jFDds|8v?2^j~NL@%86O0Z~P6E@JzIj~_3@JTphGsB=Z;+Y8)24 z)1_y`VQ|98!;phHvSLj%Z`%5!>33ZhNN#fIxV}W0M=8KuR{72D{vB&hALLjrCNYaFA%YxPIlN zWmYXqG`l1n3)OpX7YLnY6j9c(->iHqHu=M+$Lx~4^Y)6Y;h4MjCkqdUQ?{0&u%)7M z*Oibo7FSP~(paHbm+coqrftrdqAyaq{7FCemv==f<&D2GY+2{uW{i-xJ{9}T-VS1D6$Wv&L<(S13`eQyrp2VCu=M9?Q zrJmkXIi|p_wC8felTXY$k~TBWFV7H}J!2Mor=v1E1A~%?g?u4*kSowE22WQ%mvv4F FO#pA}AY}jm delta 147 zcmV;E0Brwxr2&u`e*^#k0Q|>B+yDRoY)M2xRCocs${`X!Kp240uc?xzI)tPtiWxz1 zCym>163qb=BP>6&BidbK@bcpa84aGj6s!oHt@#8D2tsQrF%vl