SEBSERV-27 #Insitution List and actions
This commit is contained in:
		
							parent
							
								
									60bd32c2cb
								
							
						
					
					
						commit
						04d438923d
					
				
					 50 changed files with 1296 additions and 335 deletions
				
			
		| 
						 | 
				
			
			@ -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;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -88,6 +88,12 @@ public final class Result<T> {
 | 
			
		|||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void handleError(final Consumer<Throwable> 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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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 */
 | 
			
		||||
    <T extends PageEvent> 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> T logoutOnError(Throwable error);
 | 
			
		||||
 | 
			
		||||
    void publishPageMessage(PageMessageException pme);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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<T extends PageEvent> {
 | 
			
		||||
 | 
			
		||||
    String LISTENER_ATTRIBUTE_KEY = "PageEventListener";
 | 
			
		||||
 | 
			
		||||
    boolean match(Class<? extends PageEvent> eventType);
 | 
			
		||||
 | 
			
		||||
    default int priority() {
 | 
			
		||||
        return 1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void notify(T event);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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<Action, Result<?>> exec;
 | 
			
		||||
    Supplier<Set<String>> 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<Action, Result<?>> exec) {
 | 
			
		||||
        this.exec = exec;
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public Action withSelectionSupplier(final Supplier<Set<String>> 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;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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),
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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);
 | 
			
		||||
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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<Action, Result<?>> editInstitution(final EntityTable<?> fromTable) {
 | 
			
		||||
        return action -> {
 | 
			
		||||
            final Collection<String> 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,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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<EntityName> insitutionNames = this.restService
 | 
			
		||||
                .getBuilder(GetInstitutionNames.class)
 | 
			
		||||
                .call()
 | 
			
		||||
                .get(pageContext::notifyError, () -> Collections.emptyList());
 | 
			
		||||
//        final List<EntityName> 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;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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<? extends TemplateComposer> contentPaneComposer;
 | 
			
		||||
        public final Class<? extends TemplateComposer> actionPaneComposer;
 | 
			
		||||
        public final String objectIdentifierAttribute;
 | 
			
		||||
        //public final String modelIdAttribute;
 | 
			
		||||
 | 
			
		||||
        private Activity(
 | 
			
		||||
                final Class<? extends TemplateComposer> objectPaneComposer,
 | 
			
		||||
| 
						 | 
				
			
			@ -65,43 +68,54 @@ public class ActivitySelection {
 | 
			
		|||
            this.title = title;
 | 
			
		||||
            this.contentPaneComposer = objectPaneComposer;
 | 
			
		||||
            this.actionPaneComposer = selectionPaneComposer;
 | 
			
		||||
            this.objectIdentifierAttribute = null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private Activity(
 | 
			
		||||
                final Class<? extends TemplateComposer> objectPaneComposer,
 | 
			
		||||
                final Class<? extends TemplateComposer> selectionPaneComposer,
 | 
			
		||||
                final String objectIdentifierAttribute) {
 | 
			
		||||
                final Class<? extends TemplateComposer> 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<String, String> attributes;
 | 
			
		||||
    Consumer<TreeItem> 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<String, String> getAttributes() {
 | 
			
		||||
        return Collections.unmodifiableMap(this.attributes);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public ActivitySelection withExpandFunction(final Consumer<TreeItem> 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) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 {
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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<Institution> 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();
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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<ActionEvent> {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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<ActionPublishEvent> {
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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<ActivitySelectionEvent> {
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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<LogoutEvent> {
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 {
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 <T> the type of the PageEvent to listen to */
 | 
			
		||||
public interface PageEventListener<T extends PageEvent> {
 | 
			
		||||
 | 
			
		||||
    /** 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<? extends PageEvent> 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);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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<String, FormFieldAccessor<?>> formFields = new LinkedHashMap<>();
 | 
			
		||||
    private final Map<String, Form> subForms = new LinkedHashMap<>();
 | 
			
		||||
    private final Map<String, List<Form>> subLists = new LinkedHashMap<>();
 | 
			
		||||
    private final Map<String, Set<String>> 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<Form> array = this.subLists.computeIfAbsent(arrayName, k -> new ArrayList<>());
 | 
			
		||||
        array.add(form);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public Form getSubForm(final String arrayName, final int index) {
 | 
			
		||||
        final List<Form> 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<String> namesSet = this.groups.get(group);
 | 
			
		||||
        process(
 | 
			
		||||
                name -> namesSet.contains(name),
 | 
			
		||||
                ffa -> ffa.setVisible(visible));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void process(
 | 
			
		||||
            final Predicate<String> nameFilter,
 | 
			
		||||
            final Consumer<FormFieldAccessor<?>> processor) {
 | 
			
		||||
 | 
			
		||||
        this.formFields.entrySet()
 | 
			
		||||
                .stream()
 | 
			
		||||
                .filter(entity -> nameFilter.test(entity.getKey()))
 | 
			
		||||
                .map(entity -> entity.getValue())
 | 
			
		||||
                .forEach(processor);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void flush() {
 | 
			
		||||
        for (final Map.Entry<String, FormFieldAccessor<?>> entry : this.formFields.entrySet()) {
 | 
			
		||||
            final FormFieldAccessor<?> accessor = entry.getValue();
 | 
			
		||||
            if (accessor.control.isVisible()) {
 | 
			
		||||
                this.objectRoot.put(entry.getKey(), accessor.getValue());
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        for (final Map.Entry<String, Form> 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<String, List<Form>> entry : this.subLists.entrySet()) {
 | 
			
		||||
            final List<Form> 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<Text> 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<SingleSelection> 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<T extends Control> {
 | 
			
		||||
 | 
			
		||||
        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;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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<Tuple<String>> items,
 | 
			
		||||
            final Consumer<Form> selectionListener) {
 | 
			
		||||
 | 
			
		||||
        return addSingleSelection(name, label, value, items, selectionListener, 1, null);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public FormBuilder addSingleSelection(
 | 
			
		||||
            final String name,
 | 
			
		||||
            final String label,
 | 
			
		||||
            final String value,
 | 
			
		||||
            final List<Tuple<String>> items,
 | 
			
		||||
            final Consumer<Form> 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<Tuple<String>> items,
 | 
			
		||||
            final Consumer<Form> 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 <T> FormHandle<T> buildFor(final RestCall<T> post) {
 | 
			
		||||
        return new FormHandle<>(this.pageContext, this.form, post, this.polyglotPageService.getI18nSupport());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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<T> {
 | 
			
		||||
 | 
			
		||||
    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<T> post;
 | 
			
		||||
    private final I18nSupport i18nSupport;
 | 
			
		||||
 | 
			
		||||
    FormHandle(
 | 
			
		||||
            final PageContext pageContext,
 | 
			
		||||
            final Form form,
 | 
			
		||||
            final RestCall<T> 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<T> process(final Consumer<Form> consumer) {
 | 
			
		||||
        consumer.accept(this.form);
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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<? extends PageDefinition> mainPageType = DefaultMainPage.class;
 | 
			
		||||
 | 
			
		||||
    final AuthorizationContextHolder authorizationContextHolder;
 | 
			
		||||
    private final RestService restService;
 | 
			
		||||
    private final I18nSupport i18nSupport;
 | 
			
		||||
    private final Map<String, TemplateComposer> composer;
 | 
			
		||||
    private final Map<String, PageDefinition> pages;
 | 
			
		||||
 | 
			
		||||
    public ComposerServiceImpl(
 | 
			
		||||
            final AuthorizationContextHolder authorizationContextHolder,
 | 
			
		||||
            final RestService restService,
 | 
			
		||||
            final I18nSupport i18nSupport,
 | 
			
		||||
            final Collection<TemplateComposer> composer,
 | 
			
		||||
            final Collection<PageDefinition> 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);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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<String, String> attributes;
 | 
			
		||||
 | 
			
		||||
    PageContextImpl(
 | 
			
		||||
            final RestService restService,
 | 
			
		||||
            final I18nSupport i18nSupport,
 | 
			
		||||
            final ComposerService composerService,
 | 
			
		||||
            final Composite root,
 | 
			
		||||
            final Composite parent,
 | 
			
		||||
            final Map<String, String> 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<String, String> 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) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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);
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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));
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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();
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -66,6 +66,9 @@ public abstract class RestCall<T> {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    protected Result<T> exchange(final RestCallBuilder builder) {
 | 
			
		||||
 | 
			
		||||
        log.debug("Call webservice API on {} for {}", this.path, builder);
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            final ResponseEntity<String> responseEntity = RestCall.this.restService
 | 
			
		||||
                    .getWebserviceAPIRestTemplate()
 | 
			
		||||
| 
						 | 
				
			
			@ -90,6 +93,11 @@ public abstract class RestCall<T> {
 | 
			
		|||
                        responseEntity.getBody(),
 | 
			
		||||
                        new TypeReference<List<APIMessage>>() {
 | 
			
		||||
                        }));
 | 
			
		||||
 | 
			
		||||
                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<T> {
 | 
			
		|||
                        new TypeReference<List<APIMessage>>() {
 | 
			
		||||
                        }));
 | 
			
		||||
            } 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<T> {
 | 
			
		|||
            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<T> {
 | 
			
		|||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public String toString() {
 | 
			
		||||
            return "RestCallBuilder [httpHeaders=" + this.httpHeaders + ", body=" + this.body + ", queryParams="
 | 
			
		||||
                    + this.queryParams
 | 
			
		||||
                    + ", uriVariables=" + this.uriVariables + "]";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -37,4 +37,8 @@ public class RestCallError extends RuntimeException implements APIMessageError {
 | 
			
		|||
        return !this.errors.isEmpty();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public String toString() {
 | 
			
		||||
        return "RestCallError [errors=" + this.errors + "]";
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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<String, RestCall<?>> calls;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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<Institution> {
 | 
			
		||||
 | 
			
		||||
    protected GetInstitution() {
 | 
			
		||||
        super(
 | 
			
		||||
                new TypeReference<Institution>() {
 | 
			
		||||
                },
 | 
			
		||||
                HttpMethod.GET,
 | 
			
		||||
                MediaType.APPLICATION_FORM_URLENCODED,
 | 
			
		||||
                SEBServerRestEndpoints.ENDPOINT_INSTITUTION);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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<Institution> {
 | 
			
		||||
 | 
			
		||||
    protected NewInstitution() {
 | 
			
		||||
        super(
 | 
			
		||||
                new TypeReference<Institution>() {
 | 
			
		||||
                },
 | 
			
		||||
                HttpMethod.POST,
 | 
			
		||||
                MediaType.APPLICATION_FORM_URLENCODED,
 | 
			
		||||
                SEBServerRestEndpoints.ENDPOINT_INSTITUTION);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -40,6 +40,15 @@ public final class ColumnDefinition<ROW extends Entity> {
 | 
			
		|||
        this(columnName, displayName, null, widthPercent, null, null, false);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public ColumnDefinition(
 | 
			
		||||
            final String columnName,
 | 
			
		||||
            final LocTextKey displayName,
 | 
			
		||||
            final Function<ROW, Object> valueSupplier,
 | 
			
		||||
            final boolean sortable) {
 | 
			
		||||
 | 
			
		||||
        this(columnName, displayName, null, -1, valueSupplier, null, sortable);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public ColumnDefinition(
 | 
			
		||||
            final String columnName,
 | 
			
		||||
            final LocTextKey displayName,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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<ROW extends Entity> 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<ROW extends Entity> extends Composite {
 | 
			
		|||
    private boolean columnsWithSameWidth = true;
 | 
			
		||||
 | 
			
		||||
    EntityTable(
 | 
			
		||||
            final int type,
 | 
			
		||||
            final Composite parent,
 | 
			
		||||
            final RestCall<Page<ROW>> restCall,
 | 
			
		||||
            final WidgetFactory widgetFactory,
 | 
			
		||||
            final List<ColumnDefinition<ROW>> columns,
 | 
			
		||||
            final List<TableRowAction> 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<ROW extends Entity> extends Composite {
 | 
			
		|||
                this.sortOrder);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public Collection<String> 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<ROW> column : this.columns) {
 | 
			
		||||
            final TableColumn tableColumn = this.widgetFactory.tableColumnLocalized(
 | 
			
		||||
| 
						 | 
				
			
			@ -204,9 +221,6 @@ public class EntityTable<ROW extends Entity> 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<ROW> column : this.columns) {
 | 
			
		||||
                final Object value = column.valueSupplier.apply(row);
 | 
			
		||||
                if (value instanceof Boolean) {
 | 
			
		||||
| 
						 | 
				
			
			@ -247,4 +261,13 @@ public class EntityTable<ROW extends Entity> 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();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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<ROW extends Entity> {
 | 
			
		|||
    final List<TableRowAction> 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<ROW extends Entity> {
 | 
			
		|||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public TableBuilder<ROW> withSelectableRows() {
 | 
			
		||||
        this.selectableRows = true;
 | 
			
		||||
    public TableBuilder<ROW> withAction(final TableRowAction action) {
 | 
			
		||||
        this.actions.add(action);
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public TableBuilder<ROW> withAction(final TableRowAction action) {
 | 
			
		||||
        this.actions.add(action);
 | 
			
		||||
    public TableBuilder<ROW> withMultiselection() {
 | 
			
		||||
        this.type |= SWT.MULTI;
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -79,14 +80,14 @@ public class TableBuilder<ROW extends Entity> {
 | 
			
		|||
                .isPresent();
 | 
			
		||||
 | 
			
		||||
        return new EntityTable<>(
 | 
			
		||||
                this.type,
 | 
			
		||||
                parent,
 | 
			
		||||
                this.restCall,
 | 
			
		||||
                this.widgetFactory,
 | 
			
		||||
                this.columns,
 | 
			
		||||
                this.actions,
 | 
			
		||||
                this.pageSize,
 | 
			
		||||
                withFilter,
 | 
			
		||||
                this.selectableRows);
 | 
			
		||||
                withFilter);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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<Tree> treeFunction(final I18nSupport i18nSupport) {
 | 
			
		||||
        return tree -> updateLocale(tree.getItems(), i18nSupport);
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
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}
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								src/main/resources/static/images/delete.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/main/resources/static/images/delete.png
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 186 B  | 
							
								
								
									
										
											BIN
										
									
								
								src/main/resources/static/images/editAction.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/main/resources/static/images/editAction.png
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 165 B  | 
										
											Binary file not shown.
										
									
								
							| 
		 Before Width: | Height: | Size: 165 B After Width: | Height: | Size: 121 B  | 
		Loading…
	
	Add table
		
		Reference in a new issue