SEBSERV-29 new multi selection with combo supporter selection
This commit is contained in:
		
							parent
							
								
									94858b757a
								
							
						
					
					
						commit
						584a900529
					
				
					 24 changed files with 535 additions and 181 deletions
				
			
		|  | @ -14,6 +14,9 @@ import org.joda.time.format.DateTimeFormatter; | ||||||
| /** Global Constants used in SEB Server web-service as well as in web-gui component */ | /** Global Constants used in SEB Server web-service as well as in web-gui component */ | ||||||
| public final class Constants { | public final class Constants { | ||||||
| 
 | 
 | ||||||
|  |     public static final String TRUE_STRING = Boolean.TRUE.toString(); | ||||||
|  |     public static final String FALSE_STRING = Boolean.FALSE.toString(); | ||||||
|  | 
 | ||||||
|     public static final long SECOND_IN_MILLIS = 1000; |     public static final long SECOND_IN_MILLIS = 1000; | ||||||
|     public static final long MINUTE_IN_MILLIS = 60 * SECOND_IN_MILLIS; |     public static final long MINUTE_IN_MILLIS = 60 * SECOND_IN_MILLIS; | ||||||
|     public static final long HOUR_IN_MILLIS = 60 * MINUTE_IN_MILLIS; |     public static final long HOUR_IN_MILLIS = 60 * MINUTE_IN_MILLIS; | ||||||
|  |  | ||||||
|  | @ -48,6 +48,7 @@ public final class UserInfo implements UserAccount, Activatable, Serializable { | ||||||
|     public static final String FILTER_ATTR_USER_NAME = "username"; |     public static final String FILTER_ATTR_USER_NAME = "username"; | ||||||
|     public static final String FILTER_ATTR_EMAIL = "email"; |     public static final String FILTER_ATTR_EMAIL = "email"; | ||||||
|     public static final String FILTER_ATTR_LANGUAGE = "language"; |     public static final String FILTER_ATTR_LANGUAGE = "language"; | ||||||
|  |     public static final String FILTER_ATTR_ROLE = "role"; | ||||||
| 
 | 
 | ||||||
|     /** The user's UUID */ |     /** The user's UUID */ | ||||||
|     @JsonProperty(USER.ATTR_UUID) |     @JsonProperty(USER.ATTR_UUID) | ||||||
|  |  | ||||||
|  | @ -10,7 +10,11 @@ package ch.ethz.seb.sebserver.gui.content; | ||||||
| 
 | 
 | ||||||
| import java.util.function.Function; | import java.util.function.Function; | ||||||
| 
 | 
 | ||||||
|  | import org.eclipse.swt.SWT; | ||||||
|  | import org.eclipse.swt.layout.GridData; | ||||||
| import org.eclipse.swt.widgets.Composite; | import org.eclipse.swt.widgets.Composite; | ||||||
|  | import org.eclipse.swt.widgets.Label; | ||||||
|  | import org.eclipse.swt.widgets.Text; | ||||||
| import org.springframework.beans.factory.annotation.Value; | import org.springframework.beans.factory.annotation.Value; | ||||||
| import org.springframework.context.annotation.Lazy; | import org.springframework.context.annotation.Lazy; | ||||||
| import org.springframework.stereotype.Component; | import org.springframework.stereotype.Component; | ||||||
|  | @ -28,6 +32,7 @@ import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition; | ||||||
| import ch.ethz.seb.sebserver.gui.service.ResourceService; | import ch.ethz.seb.sebserver.gui.service.ResourceService; | ||||||
| import ch.ethz.seb.sebserver.gui.service.i18n.I18nSupport; | 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.LocTextKey; | ||||||
|  | import ch.ethz.seb.sebserver.gui.service.page.ModalInputDialog; | ||||||
| import ch.ethz.seb.sebserver.gui.service.page.PageContext; | 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.TemplateComposer; | ||||||
| import ch.ethz.seb.sebserver.gui.service.page.action.Action; | import ch.ethz.seb.sebserver.gui.service.page.action.Action; | ||||||
|  | @ -131,6 +136,10 @@ public class ExamList implements TemplateComposer { | ||||||
|         final GrantCheck userGrant = currentUser.grantCheck(EntityType.EXAM); |         final GrantCheck userGrant = currentUser.grantCheck(EntityType.EXAM); | ||||||
|         pageContext.clearEntityKeys() |         pageContext.clearEntityKeys() | ||||||
| 
 | 
 | ||||||
|  |                 .createAction(ActionDefinition.TEST_ACTION) | ||||||
|  |                 .withExec(this::testModalInput) | ||||||
|  |                 .publish() | ||||||
|  | 
 | ||||||
|                 .createAction(ActionDefinition.EXAM_IMPORT) |                 .createAction(ActionDefinition.EXAM_IMPORT) | ||||||
|                 .publishIf(userGrant::im) |                 .publishIf(userGrant::im) | ||||||
| 
 | 
 | ||||||
|  | @ -163,4 +172,29 @@ public class ExamList implements TemplateComposer { | ||||||
|                 .getText("sebserver.exam.type." + exam.type.name()); |                 .getText("sebserver.exam.type." + exam.type.name()); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     private Action testModalInput(final Action action) { | ||||||
|  |         final ModalInputDialog<String> dialog = new ModalInputDialog<>( | ||||||
|  |                 action.pageContext().getParent().getShell(), | ||||||
|  |                 this.widgetFactory); | ||||||
|  | 
 | ||||||
|  |         dialog.open( | ||||||
|  |                 "Test Input Dialog", | ||||||
|  |                 action.pageContext(), | ||||||
|  |                 value -> { | ||||||
|  |                     System.out.println("********************** value: " + value); | ||||||
|  |                 }, | ||||||
|  |                 pc -> { | ||||||
|  |                     final Composite parent = pc.getParent(); | ||||||
|  |                     final Label label = new Label(parent, SWT.NONE); | ||||||
|  |                     label.setText("Please Enter:"); | ||||||
|  |                     label.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); | ||||||
|  | 
 | ||||||
|  |                     final Text text = new Text(parent, SWT.LEFT | SWT.BORDER); | ||||||
|  |                     text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); | ||||||
|  |                     return () -> text.getText(); | ||||||
|  |                 }); | ||||||
|  | 
 | ||||||
|  |         return action; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -319,7 +319,7 @@ public enum ActionDefinition { | ||||||
|     EXAM_INDICATOR_NEW( |     EXAM_INDICATOR_NEW( | ||||||
|             new LocTextKey("sebserver.exam.indicator.action.list.new"), |             new LocTextKey("sebserver.exam.indicator.action.list.new"), | ||||||
|             ImageIcon.NEW, |             ImageIcon.NEW, | ||||||
|             LmsSetupForm.class, |             IndicatorForm.class, | ||||||
|             EXAM_VIEW_FROM_LIST, |             EXAM_VIEW_FROM_LIST, | ||||||
|             ActionCategory.FORM, |             ActionCategory.FORM, | ||||||
|             false), |             false), | ||||||
|  | @ -350,6 +350,12 @@ public enum ActionDefinition { | ||||||
|             EXAM_VIEW_FROM_LIST, |             EXAM_VIEW_FROM_LIST, | ||||||
|             ActionCategory.FORM, |             ActionCategory.FORM, | ||||||
|             true), |             true), | ||||||
|  |     TEST_ACTION( | ||||||
|  |             new LocTextKey("TEST"), | ||||||
|  |             ImageIcon.TEST, | ||||||
|  |             ExamList.class, | ||||||
|  |             EXAM_VIEW_LIST, | ||||||
|  |             ActionCategory.FORM), | ||||||
| 
 | 
 | ||||||
|     ; |     ; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -8,12 +8,12 @@ | ||||||
| 
 | 
 | ||||||
| package ch.ethz.seb.sebserver.gui.form; | package ch.ethz.seb.sebserver.gui.form; | ||||||
| 
 | 
 | ||||||
| import java.util.ArrayList; |  | ||||||
| import java.util.HashSet; | import java.util.HashSet; | ||||||
| import java.util.LinkedHashMap; | import java.util.LinkedHashMap; | ||||||
| import java.util.List; | import java.util.List; | ||||||
| import java.util.Map; | import java.util.Map; | ||||||
| import java.util.Set; | import java.util.Set; | ||||||
|  | import java.util.function.BiConsumer; | ||||||
| import java.util.function.Consumer; | import java.util.function.Consumer; | ||||||
| import java.util.function.Predicate; | import java.util.function.Predicate; | ||||||
| 
 | 
 | ||||||
|  | @ -30,10 +30,10 @@ import com.fasterxml.jackson.databind.node.ObjectNode; | ||||||
| 
 | 
 | ||||||
| import ch.ethz.seb.sebserver.gbl.Constants; | import ch.ethz.seb.sebserver.gbl.Constants; | ||||||
| import ch.ethz.seb.sebserver.gbl.api.JSONMapper; | import ch.ethz.seb.sebserver.gbl.api.JSONMapper; | ||||||
|  | import ch.ethz.seb.sebserver.gbl.util.Tuple; | ||||||
| import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.FormBinding; | import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.FormBinding; | ||||||
| import ch.ethz.seb.sebserver.gui.widget.ImageUpload; | import ch.ethz.seb.sebserver.gui.widget.ImageUpload; | ||||||
| import ch.ethz.seb.sebserver.gui.widget.MultiSelection; | import ch.ethz.seb.sebserver.gui.widget.Selection; | ||||||
| import ch.ethz.seb.sebserver.gui.widget.SingleSelection; |  | ||||||
| 
 | 
 | ||||||
| public final class Form implements FormBinding { | public final class Form implements FormBinding { | ||||||
| 
 | 
 | ||||||
|  | @ -42,8 +42,6 @@ public final class Form implements FormBinding { | ||||||
| 
 | 
 | ||||||
|     private final Map<String, String> staticValues = new LinkedHashMap<>(); |     private final Map<String, String> staticValues = new LinkedHashMap<>(); | ||||||
|     private final MultiValueMap<String, FormFieldAccessor> formFields = new LinkedMultiValueMap<>(); |     private final MultiValueMap<String, FormFieldAccessor> formFields = new LinkedMultiValueMap<>(); | ||||||
|     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<>(); |     private final Map<String, Set<String>> groups = new LinkedHashMap<>(); | ||||||
| 
 | 
 | ||||||
|     Form(final JSONMapper jsonMapper) { |     Form(final JSONMapper jsonMapper) { | ||||||
|  | @ -104,40 +102,23 @@ public final class Form implements FormBinding { | ||||||
|         return this; |         return this; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public void putField(final String name, final Label label, final SingleSelection field) { |     public void putField(final String name, final Label label, final Selection field) { | ||||||
|         this.formFields.add(name, createAccessor(label, field)); |         this.formFields.add(name, createAccessor(label, field)); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public void putField(final String name, final Label label, final MultiSelection field) { |     public void putField( | ||||||
|         this.formFields.add(name, createAccessor(label, field)); |             final String name, | ||||||
|  |             final Label label, | ||||||
|  |             final Selection field, | ||||||
|  |             final BiConsumer<Tuple<String>, ObjectNode> jsonValueAdapter) { | ||||||
|  | 
 | ||||||
|  |         this.formFields.add(name, createAccessor(label, field, jsonValueAdapter)); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public void putField(final String name, final Label label, final ImageUpload imageUpload) { |     public void putField(final String name, final Label label, final ImageUpload imageUpload) { | ||||||
|         this.formFields.add(name, createAccessor(label, imageUpload)); |         this.formFields.add(name, createAccessor(label, imageUpload)); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     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() { |     public void allVisible() { | ||||||
|         process( |         process( | ||||||
|                 name -> true, |                 name -> true, | ||||||
|  | @ -196,24 +177,6 @@ public final class Form implements FormBinding { | ||||||
|                     .filter(ffa -> StringUtils.isNoneBlank(ffa.getValue())) |                     .filter(ffa -> StringUtils.isNoneBlank(ffa.getValue())) | ||||||
|                     .forEach(ffa -> ffa.putJsonValue(entry.getKey(), this.objectRoot)); |                     .forEach(ffa -> ffa.putJsonValue(entry.getKey(), this.objectRoot)); | ||||||
|         } |         } | ||||||
| 
 |  | ||||||
|         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); |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // following are FormFieldAccessor implementations for all field types |     // following are FormFieldAccessor implementations for all field types | ||||||
|  | @ -230,26 +193,20 @@ public final class Form implements FormBinding { | ||||||
|             @Override public void setValue(final String value) { text.setText(value); } |             @Override public void setValue(final String value) { text.setText(value); } | ||||||
|         }; |         }; | ||||||
|     } |     } | ||||||
|     private FormFieldAccessor createAccessor(final Label label, final SingleSelection singleSelection) { |     private FormFieldAccessor createAccessor(final Label label, final Selection selection) { | ||||||
|         return new FormFieldAccessor(label, singleSelection) { |         switch (selection.type()) { | ||||||
|             @Override public String getValue() { return singleSelection.getSelectionValue(); } |             case MULTI : return createAccessor(label, selection, Form::adaptCommaSeparatedStringToJsonArray); | ||||||
|             @Override public void setValue(final String value) { singleSelection.select(value); } |             default : return createAccessor(label, selection, null); | ||||||
|         }; |  | ||||||
|     } |  | ||||||
|     private FormFieldAccessor createAccessor(final Label label,final MultiSelection multiSelection) { |  | ||||||
|         return new FormFieldAccessor(label, multiSelection) { |  | ||||||
|             @Override public String getValue() { return multiSelection.getSelectionValue(); } |  | ||||||
|             @Override public void setValue(final String value) { multiSelection.select(value); } |  | ||||||
|             @Override public void putJsonValue(final String key, final ObjectNode objectRoot) { |  | ||||||
|                 final String value = getValue(); |  | ||||||
|                 if (StringUtils.isNoneBlank(value)) { |  | ||||||
|                     final ArrayNode arrayNode = objectRoot.putArray(key); |  | ||||||
|                     final String[] split = StringUtils.split(value, Constants.LIST_SEPARATOR); |  | ||||||
|                     for (int i = 0; i < split.length; i++) { |  | ||||||
|                     arrayNode.add(split[i]); |  | ||||||
|                     } |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |     private FormFieldAccessor createAccessor( | ||||||
|  |             final Label label, | ||||||
|  |             final Selection selection, | ||||||
|  |             final BiConsumer<Tuple<String>, ObjectNode> jsonValueAdapter) { | ||||||
|  | 
 | ||||||
|  |         return new FormFieldAccessor(label, selection.adaptToControl(), jsonValueAdapter) { | ||||||
|  |             @Override public String getValue() { return selection.getSelectionValue(); } | ||||||
|  |             @Override public void setValue(final String value) { selection.select(value); } | ||||||
|         }; |         }; | ||||||
|     } |     } | ||||||
|     private FormFieldAccessor createAccessor(final Label label, final ImageUpload imageUpload) { |     private FormFieldAccessor createAccessor(final Label label, final ImageUpload imageUpload) { | ||||||
|  | @ -281,15 +238,44 @@ public final class Form implements FormBinding { | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     private static final void adaptCommaSeparatedStringToJsonArray(final Tuple<String> tuple, | ||||||
|  |             final ObjectNode jsonNode) { | ||||||
|  |         if (StringUtils.isNoneBlank(tuple._2)) { | ||||||
|  |             final ArrayNode arrayNode = jsonNode.putArray(tuple._1); | ||||||
|  |             final String[] split = StringUtils.split(tuple._2, Constants.LIST_SEPARATOR); | ||||||
|  |             for (int i = 0; i < split.length; i++) { | ||||||
|  |                 arrayNode.add(split[i]); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     public static abstract class FormFieldAccessor { |     public static abstract class FormFieldAccessor { | ||||||
| 
 | 
 | ||||||
|         public final Label label; |         public final Label label; | ||||||
|         public final Control control; |         public final Control control; | ||||||
|  |         private final BiConsumer<Tuple<String>, ObjectNode> jsonValueAdapter; | ||||||
|         private boolean hasError; |         private boolean hasError; | ||||||
| 
 | 
 | ||||||
|         public FormFieldAccessor(final Label label, final Control control) { |         FormFieldAccessor(final Label label, final Control control) { | ||||||
|  |             this(label, control, null); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         FormFieldAccessor( | ||||||
|  |                 final Label label, | ||||||
|  |                 final Control control, | ||||||
|  |                 final BiConsumer<Tuple<String>, ObjectNode> jsonValueAdapter) { | ||||||
|  | 
 | ||||||
|             this.label = label; |             this.label = label; | ||||||
|             this.control = control; |             this.control = control; | ||||||
|  |             if (jsonValueAdapter != null) { | ||||||
|  |                 this.jsonValueAdapter = jsonValueAdapter; | ||||||
|  |             } else { | ||||||
|  |                 this.jsonValueAdapter = (tuple, jsonObject) -> { | ||||||
|  |                     if (StringUtils.isNoneBlank(tuple._2)) { | ||||||
|  |                         jsonObject.put(tuple._1, tuple._2); | ||||||
|  |                     } | ||||||
|  |                 }; | ||||||
|  |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         public abstract String getValue(); |         public abstract String getValue(); | ||||||
|  | @ -301,11 +287,8 @@ public final class Form implements FormBinding { | ||||||
|             this.control.setVisible(visible); |             this.control.setVisible(visible); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         public void putJsonValue(final String key, final ObjectNode objectRoot) { |         public final void putJsonValue(final String key, final ObjectNode objectRoot) { | ||||||
|             final String value = getValue(); |             this.jsonValueAdapter.accept(new Tuple<>(key, getValue()), objectRoot); | ||||||
|             if (StringUtils.isNoneBlank(value)) { |  | ||||||
|                 objectRoot.put(key, value); |  | ||||||
|             } |  | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         public void setError(final String errorTooltip) { |         public void setError(final String errorTooltip) { | ||||||
|  |  | ||||||
|  | @ -30,6 +30,7 @@ import ch.ethz.seb.sebserver.gbl.util.Tuple; | ||||||
| import ch.ethz.seb.sebserver.gui.service.i18n.PolyglotPageService; | 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.page.PageContext; | ||||||
| import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall; | import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall; | ||||||
|  | import ch.ethz.seb.sebserver.gui.widget.Selection; | ||||||
| import ch.ethz.seb.sebserver.gui.widget.WidgetFactory; | import ch.ethz.seb.sebserver.gui.widget.WidgetFactory; | ||||||
| import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.CustomVariant; | import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.CustomVariant; | ||||||
| 
 | 
 | ||||||
|  | @ -194,7 +195,7 @@ public class FormBuilder { | ||||||
|             final String value, |             final String value, | ||||||
|             final Supplier<List<Tuple<String>>> itemsSupplier) { |             final Supplier<List<Tuple<String>>> itemsSupplier) { | ||||||
| 
 | 
 | ||||||
|         return new SelectionFieldBuilder(name, label, value, itemsSupplier); |         return new SelectionFieldBuilder(Selection.Type.SINGLE, name, label, value, itemsSupplier); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public static SelectionFieldBuilder multiSelection( |     public static SelectionFieldBuilder multiSelection( | ||||||
|  | @ -203,8 +204,16 @@ public class FormBuilder { | ||||||
|             final String value, |             final String value, | ||||||
|             final Supplier<List<Tuple<String>>> itemsSupplier) { |             final Supplier<List<Tuple<String>>> itemsSupplier) { | ||||||
| 
 | 
 | ||||||
|         return new SelectionFieldBuilder(name, label, value, itemsSupplier) |         return new SelectionFieldBuilder(Selection.Type.MULTI, name, label, value, itemsSupplier); | ||||||
|                 .asMultiSelection(); |     } | ||||||
|  | 
 | ||||||
|  |     public static SelectionFieldBuilder multiComboSelection( | ||||||
|  |             final String name, | ||||||
|  |             final String label, | ||||||
|  |             final String value, | ||||||
|  |             final Supplier<List<Tuple<String>>> itemsSupplier) { | ||||||
|  | 
 | ||||||
|  |         return new SelectionFieldBuilder(Selection.Type.MULTI_COMBO, name, label, value, itemsSupplier); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public static ImageUploadFieldBuilder imageUpload(final String name, final String label, final String value) { |     public static ImageUploadFieldBuilder imageUpload(final String name, final String label, final String value) { | ||||||
|  |  | ||||||
|  | @ -26,24 +26,25 @@ import org.eclipse.swt.widgets.Label; | ||||||
| import ch.ethz.seb.sebserver.gbl.Constants; | import ch.ethz.seb.sebserver.gbl.Constants; | ||||||
| import ch.ethz.seb.sebserver.gbl.util.Tuple; | import ch.ethz.seb.sebserver.gbl.util.Tuple; | ||||||
| import ch.ethz.seb.sebserver.gui.service.i18n.PolyglotPageService; | import ch.ethz.seb.sebserver.gui.service.i18n.PolyglotPageService; | ||||||
| import ch.ethz.seb.sebserver.gui.widget.MultiSelection; |  | ||||||
| import ch.ethz.seb.sebserver.gui.widget.Selection; | import ch.ethz.seb.sebserver.gui.widget.Selection; | ||||||
| import ch.ethz.seb.sebserver.gui.widget.SingleSelection; | import ch.ethz.seb.sebserver.gui.widget.Selection.Type; | ||||||
| import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.CustomVariant; | import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.CustomVariant; | ||||||
| 
 | 
 | ||||||
| public final class SelectionFieldBuilder extends FieldBuilder { | public final class SelectionFieldBuilder extends FieldBuilder { | ||||||
| 
 | 
 | ||||||
|     final Supplier<List<Tuple<String>>> itemsSupplier; |     final Supplier<List<Tuple<String>>> itemsSupplier; | ||||||
|     Consumer<Form> selectionListener = null; |     Consumer<Form> selectionListener = null; | ||||||
|     boolean multi = false; |     final Selection.Type type; | ||||||
| 
 | 
 | ||||||
|     SelectionFieldBuilder( |     SelectionFieldBuilder( | ||||||
|  |             final Selection.Type type, | ||||||
|             final String name, |             final String name, | ||||||
|             final String label, |             final String label, | ||||||
|             final String value, |             final String value, | ||||||
|             final Supplier<List<Tuple<String>>> itemsSupplier) { |             final Supplier<List<Tuple<String>>> itemsSupplier) { | ||||||
| 
 | 
 | ||||||
|         super(name, label, value); |         super(name, label, value); | ||||||
|  |         this.type = type; | ||||||
|         this.itemsSupplier = itemsSupplier; |         this.itemsSupplier = itemsSupplier; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -52,11 +53,6 @@ public final class SelectionFieldBuilder extends FieldBuilder { | ||||||
|         return this; |         return this; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public SelectionFieldBuilder asMultiSelection() { |  | ||||||
|         this.multi = true; |  | ||||||
|         return this; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Override |     @Override | ||||||
|     void build(final FormBuilder builder) { |     void build(final FormBuilder builder) { | ||||||
|         final Label lab = builder.labelLocalized(builder.formParent, this.label, this.spanLabel); |         final Label lab = builder.labelLocalized(builder.formParent, this.label, this.spanLabel); | ||||||
|  | @ -68,29 +64,29 @@ public final class SelectionFieldBuilder extends FieldBuilder { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private void buildInput(final FormBuilder builder, final Label lab) { |     private void buildInput(final FormBuilder builder, final Label lab) { | ||||||
|         final Selection selection = (this.multi) | 
 | ||||||
|                 ? builder.widgetFactory.multiSelectionLocalized(builder.formParent, this.itemsSupplier) |         final Selection selection = builder.widgetFactory.selectionLocalized( | ||||||
|                 : builder.widgetFactory.singleSelectionLocalized(builder.formParent, this.itemsSupplier); |                 this.type, | ||||||
|  |                 builder.formParent, | ||||||
|  |                 this.itemsSupplier); | ||||||
| 
 | 
 | ||||||
|         final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, false, this.spanInput, 1); |         final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, false, this.spanInput, 1); | ||||||
|         ((Control) selection).setLayoutData(gridData); |         ((Control) selection).setLayoutData(gridData); | ||||||
|         selection.select(this.value); |         selection.select(this.value); | ||||||
|         if (this.multi) { |         builder.form.putField(this.name, lab, selection); | ||||||
|             builder.form.putField(this.name, lab, selection.<MultiSelection> getTypeInstance()); | 
 | ||||||
|         } else { |  | ||||||
|             builder.form.putField(this.name, lab, selection.<SingleSelection> getTypeInstance()); |  | ||||||
|         } |  | ||||||
|         if (this.selectionListener != null) { |         if (this.selectionListener != null) { | ||||||
|             ((Control) selection).addListener(SWT.Selection, e -> { |             ((Control) selection).addListener(SWT.Selection, e -> { | ||||||
|                 this.selectionListener.accept(builder.form); |                 this.selectionListener.accept(builder.form); | ||||||
|             }); |             }); | ||||||
|         } |         } | ||||||
|  | 
 | ||||||
|         builder.setFieldVisible(this.visible, this.name); |         builder.setFieldVisible(this.visible, this.name); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /* Build the read-only representation of the selection field */ |     /* Build the read-only representation of the selection field */ | ||||||
|     private void buildReadOnly(final FormBuilder builder, final Label lab) { |     private void buildReadOnly(final FormBuilder builder, final Label lab) { | ||||||
|         if (this.multi) { |         if (this.type == Type.MULTI || this.type == Type.MULTI_COMBO) { | ||||||
|             final Composite composite = new Composite(builder.formParent, SWT.NONE); |             final Composite composite = new Composite(builder.formParent, SWT.NONE); | ||||||
|             final GridLayout gridLayout = new GridLayout(1, true); |             final GridLayout gridLayout = new GridLayout(1, true); | ||||||
|             gridLayout.horizontalSpacing = 0; |             gridLayout.horizontalSpacing = 0; | ||||||
|  | @ -99,7 +95,11 @@ public final class SelectionFieldBuilder extends FieldBuilder { | ||||||
|             gridLayout.marginWidth = 0; |             gridLayout.marginWidth = 0; | ||||||
|             composite.setLayout(gridLayout); |             composite.setLayout(gridLayout); | ||||||
|             if (StringUtils.isBlank(this.value)) { |             if (StringUtils.isBlank(this.value)) { | ||||||
|                 createMuliSelectionReadonlyLabel(composite, Constants.EMPTY_NOTE); |                 final Label label = new Label(composite, SWT.NONE); | ||||||
|  |                 final GridData gridData = new GridData(SWT.FILL, SWT.CENTER, true, true); | ||||||
|  |                 label.setLayoutData(gridData); | ||||||
|  |                 label.setData(RWT.CUSTOM_VARIANT, CustomVariant.SELECTION_READONLY.key); | ||||||
|  |                 label.setText(this.value); | ||||||
|             } else { |             } else { | ||||||
|                 final Collection<String> keys = Arrays.asList(StringUtils.split(this.value, Constants.LIST_SEPARATOR)); |                 final Collection<String> keys = Arrays.asList(StringUtils.split(this.value, Constants.LIST_SEPARATOR)); | ||||||
|                 this.itemsSupplier.get() |                 this.itemsSupplier.get() | ||||||
|  | @ -137,12 +137,4 @@ public final class SelectionFieldBuilder extends FieldBuilder { | ||||||
|         return label; |         return label; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private void createMuliSelectionReadonlyLabel(final Composite composite, final String value) { |  | ||||||
|         final Label label = new Label(composite, SWT.NONE); |  | ||||||
|         final GridData gridData = new GridData(SWT.FILL, SWT.CENTER, true, true); |  | ||||||
|         label.setLayoutData(gridData); |  | ||||||
|         label.setData(RWT.CUSTOM_VARIANT, CustomVariant.SELECTION_READONLY.key); |  | ||||||
|         label.setText(value); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| } | } | ||||||
|  | @ -23,7 +23,7 @@ import org.springframework.context.annotation.Lazy; | ||||||
| import org.springframework.stereotype.Service; | import org.springframework.stereotype.Service; | ||||||
| 
 | 
 | ||||||
| import ch.ethz.seb.sebserver.gbl.Constants; | import ch.ethz.seb.sebserver.gbl.Constants; | ||||||
| import ch.ethz.seb.sebserver.gbl.model.Domain; | import ch.ethz.seb.sebserver.gbl.model.Entity; | ||||||
| import ch.ethz.seb.sebserver.gbl.model.exam.Exam.ExamType; | import ch.ethz.seb.sebserver.gbl.model.exam.Exam.ExamType; | ||||||
| import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.IndicatorType; | import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.IndicatorType; | ||||||
| import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup.LmsType; | import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup.LmsType; | ||||||
|  | @ -104,7 +104,7 @@ public class ResourceService { | ||||||
| 
 | 
 | ||||||
|     public List<Tuple<String>> institutionResource() { |     public List<Tuple<String>> institutionResource() { | ||||||
|         return this.restService.getBuilder(GetInstitutionNames.class) |         return this.restService.getBuilder(GetInstitutionNames.class) | ||||||
|                 .withQueryParam(Domain.INSTITUTION.ATTR_ACTIVE, "true") |                 .withQueryParam(Entity.FILTER_ATTR_ACTIVE, Constants.TRUE_STRING) | ||||||
|                 .call() |                 .call() | ||||||
|                 .getOr(Collections.emptyList()) |                 .getOr(Collections.emptyList()) | ||||||
|                 .stream() |                 .stream() | ||||||
|  | @ -115,7 +115,7 @@ public class ResourceService { | ||||||
|     public Function<String, String> getInstitutionNameFunction() { |     public Function<String, String> getInstitutionNameFunction() { | ||||||
| 
 | 
 | ||||||
|         final Map<String, String> idNameMap = this.restService.getBuilder(GetInstitutionNames.class) |         final Map<String, String> idNameMap = this.restService.getBuilder(GetInstitutionNames.class) | ||||||
|                 .withQueryParam(Domain.INSTITUTION.ATTR_ACTIVE, "true") |                 .withQueryParam(Entity.FILTER_ATTR_ACTIVE, Constants.TRUE_STRING) | ||||||
|                 .call() |                 .call() | ||||||
|                 .getOr(Collections.emptyList()) |                 .getOr(Collections.emptyList()) | ||||||
|                 .stream() |                 .stream() | ||||||
|  | @ -133,8 +133,8 @@ public class ResourceService { | ||||||
|         final boolean isSEBAdmin = this.currentUser.get().hasRole(UserRole.SEB_SERVER_ADMIN); |         final boolean isSEBAdmin = this.currentUser.get().hasRole(UserRole.SEB_SERVER_ADMIN); | ||||||
|         final String institutionId = (isSEBAdmin) ? "" : String.valueOf(this.currentUser.get().institutionId); |         final String institutionId = (isSEBAdmin) ? "" : String.valueOf(this.currentUser.get().institutionId); | ||||||
|         return this.restService.getBuilder(GetLmsSetupNames.class) |         return this.restService.getBuilder(GetLmsSetupNames.class) | ||||||
|                 .withQueryParam(Domain.LMS_SETUP.ATTR_INSTITUTION_ID, institutionId) |                 .withQueryParam(Entity.FILTER_ATTR_INSTITUTION, institutionId) | ||||||
|                 .withQueryParam(Domain.LMS_SETUP.ATTR_ACTIVE, "true") |                 .withQueryParam(Entity.FILTER_ATTR_ACTIVE, Constants.TRUE_STRING) | ||||||
|                 .call() |                 .call() | ||||||
|                 .getOr(Collections.emptyList()) |                 .getOr(Collections.emptyList()) | ||||||
|                 .stream() |                 .stream() | ||||||
|  | @ -146,8 +146,8 @@ public class ResourceService { | ||||||
|         final boolean isSEBAdmin = this.currentUser.get().hasRole(UserRole.SEB_SERVER_ADMIN); |         final boolean isSEBAdmin = this.currentUser.get().hasRole(UserRole.SEB_SERVER_ADMIN); | ||||||
|         final String institutionId = (isSEBAdmin) ? "" : String.valueOf(this.currentUser.get().institutionId); |         final String institutionId = (isSEBAdmin) ? "" : String.valueOf(this.currentUser.get().institutionId); | ||||||
|         final Map<String, String> idNameMap = this.restService.getBuilder(GetLmsSetupNames.class) |         final Map<String, String> idNameMap = this.restService.getBuilder(GetLmsSetupNames.class) | ||||||
|                 .withQueryParam(Domain.LMS_SETUP.ATTR_INSTITUTION_ID, institutionId) |                 .withQueryParam(Entity.FILTER_ATTR_INSTITUTION, institutionId) | ||||||
|                 .withQueryParam(Domain.INSTITUTION.ATTR_ACTIVE, "true") |                 .withQueryParam(Entity.FILTER_ATTR_ACTIVE, Constants.TRUE_STRING) | ||||||
|                 .call() |                 .call() | ||||||
|                 .getOr(Collections.emptyList()) |                 .getOr(Collections.emptyList()) | ||||||
|                 .stream() |                 .stream() | ||||||
|  | @ -196,11 +196,24 @@ public class ResourceService { | ||||||
|                 .collect(Collectors.toList()); |                 .collect(Collectors.toList()); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     public List<Tuple<String>> examSupporterResources() { | ||||||
|  |         final UserInfo userInfo = this.currentUser.get(); | ||||||
|  |         return this.restService.getBuilder(GetUserAccountNames.class) | ||||||
|  |                 .withQueryParam(Entity.FILTER_ATTR_INSTITUTION, String.valueOf(userInfo.institutionId)) | ||||||
|  |                 .withQueryParam(Entity.FILTER_ATTR_ACTIVE, Constants.TRUE_STRING) | ||||||
|  |                 .withQueryParam(UserInfo.FILTER_ATTR_ROLE, UserRole.EXAM_SUPPORTER.name()) | ||||||
|  |                 .call() | ||||||
|  |                 .getOr(Collections.emptyList()) | ||||||
|  |                 .stream() | ||||||
|  |                 .map(entityName -> new Tuple<>(entityName.modelId, entityName.name)) | ||||||
|  |                 .collect(Collectors.toList()); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     public List<Tuple<String>> userResources() { |     public List<Tuple<String>> userResources() { | ||||||
|         final UserInfo userInfo = this.currentUser.get(); |         final UserInfo userInfo = this.currentUser.get(); | ||||||
|         return this.restService.getBuilder(GetUserAccountNames.class) |         return this.restService.getBuilder(GetUserAccountNames.class) | ||||||
|                 .withQueryParam(Domain.USER.ATTR_INSTITUTION_ID, String.valueOf(userInfo.institutionId)) |                 .withQueryParam(Entity.FILTER_ATTR_INSTITUTION, String.valueOf(userInfo.institutionId)) | ||||||
|                 .withQueryParam(Domain.USER.ATTR_ACTIVE, "true") |                 .withQueryParam(Entity.FILTER_ATTR_ACTIVE, Constants.TRUE_STRING) | ||||||
|                 .call() |                 .call() | ||||||
|                 .getOr(Collections.emptyList()) |                 .getOr(Collections.emptyList()) | ||||||
|                 .stream() |                 .stream() | ||||||
|  |  | ||||||
|  | @ -0,0 +1,101 @@ | ||||||
|  | /* | ||||||
|  |  * 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; | ||||||
|  | 
 | ||||||
|  | import java.util.function.Consumer; | ||||||
|  | import java.util.function.Supplier; | ||||||
|  | 
 | ||||||
|  | import org.eclipse.rap.rwt.RWT; | ||||||
|  | import org.eclipse.swt.SWT; | ||||||
|  | import org.eclipse.swt.graphics.Rectangle; | ||||||
|  | import org.eclipse.swt.layout.GridData; | ||||||
|  | import org.eclipse.swt.layout.GridLayout; | ||||||
|  | import org.eclipse.swt.widgets.Button; | ||||||
|  | import org.eclipse.swt.widgets.Composite; | ||||||
|  | import org.eclipse.swt.widgets.Dialog; | ||||||
|  | import org.eclipse.swt.widgets.Shell; | ||||||
|  | 
 | ||||||
|  | import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey; | ||||||
|  | import ch.ethz.seb.sebserver.gui.widget.WidgetFactory; | ||||||
|  | 
 | ||||||
|  | public class ModalInputDialog<T> extends Dialog { | ||||||
|  | 
 | ||||||
|  |     private static final long serialVersionUID = -3448614119078234374L; | ||||||
|  | 
 | ||||||
|  |     private final WidgetFactory widgetFactory; | ||||||
|  | 
 | ||||||
|  |     private T returnValue = null; | ||||||
|  | 
 | ||||||
|  |     public ModalInputDialog( | ||||||
|  |             final Shell parent, | ||||||
|  |             final WidgetFactory widgetFactory) { | ||||||
|  | 
 | ||||||
|  |         super(parent, SWT.BORDER | SWT.TITLE | SWT.APPLICATION_MODAL); | ||||||
|  |         this.widgetFactory = widgetFactory; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public void open( | ||||||
|  |             final String title, | ||||||
|  |             final PageContext pageContext, | ||||||
|  |             final Consumer<T> callback, | ||||||
|  |             final ModalInputDialogComposer<T> contentComposer) { | ||||||
|  | 
 | ||||||
|  |         // Create the dialog window | ||||||
|  |         final Shell shell = new Shell(getParent(), getStyle()); | ||||||
|  |         shell.setText(getText()); | ||||||
|  |         shell.setData(RWT.CUSTOM_VARIANT, "message"); | ||||||
|  |         shell.setText(title); | ||||||
|  |         shell.setLayout(new GridLayout(2, true)); | ||||||
|  |         shell.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); | ||||||
|  | 
 | ||||||
|  |         final Composite main = new Composite(shell, SWT.NONE); | ||||||
|  |         main.setLayout(new GridLayout()); | ||||||
|  |         final GridData gridData = new GridData(SWT.FILL, SWT.TOP, true, true); | ||||||
|  |         gridData.horizontalSpan = 2; | ||||||
|  |         main.setLayoutData(gridData); | ||||||
|  | 
 | ||||||
|  |         final PageContext internalPageContext = pageContext.copyOf(main); | ||||||
|  |         final Supplier<T> valueSuppier = contentComposer.compose(internalPageContext); | ||||||
|  | 
 | ||||||
|  |         final Button ok = this.widgetFactory.buttonLocalized( | ||||||
|  |                 shell, | ||||||
|  |                 new LocTextKey("sebserver.overall.action.ok")); | ||||||
|  |         GridData data = new GridData(GridData.HORIZONTAL_ALIGN_END); | ||||||
|  |         data.widthHint = 100; | ||||||
|  |         ok.setLayoutData(data); | ||||||
|  |         ok.addListener(SWT.Selection, event -> { | ||||||
|  |             callback.accept(valueSuppier.get()); | ||||||
|  |             this.returnValue = valueSuppier.get(); | ||||||
|  |             shell.close(); | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         shell.setDefaultButton(ok); | ||||||
|  | 
 | ||||||
|  |         final Button cancel = this.widgetFactory.buttonLocalized( | ||||||
|  |                 shell, | ||||||
|  |                 new LocTextKey("sebserver.overall.action.cancel")); | ||||||
|  |         data = new GridData(GridData.CENTER); | ||||||
|  |         data.widthHint = 100; | ||||||
|  |         cancel.setLayoutData(data); | ||||||
|  |         cancel.addListener(SWT.Selection, event -> { | ||||||
|  |             shell.close(); | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         shell.pack(); | ||||||
|  |         final Rectangle bounds = shell.getBounds(); | ||||||
|  |         final Rectangle bounds2 = super.getParent().getDisplay().getBounds(); | ||||||
|  |         bounds.width = 400; | ||||||
|  |         bounds.x = (bounds2.width - bounds.width) / 2; | ||||||
|  |         bounds.y = (bounds2.height - bounds.height) / 2; | ||||||
|  |         shell.setBounds(bounds); | ||||||
|  | 
 | ||||||
|  |         shell.open(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | @ -0,0 +1,18 @@ | ||||||
|  | /* | ||||||
|  |  * 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; | ||||||
|  | 
 | ||||||
|  | import java.util.function.Supplier; | ||||||
|  | 
 | ||||||
|  | @FunctionalInterface | ||||||
|  | public interface ModalInputDialogComposer<T> { | ||||||
|  | 
 | ||||||
|  |     Supplier<T> compose(PageContext pageContext); | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | @ -325,10 +325,6 @@ public class EntityTable<ROW extends Entity> { | ||||||
| 
 | 
 | ||||||
|                 index++; |                 index++; | ||||||
|             } |             } | ||||||
| 
 |  | ||||||
|             // NOTE this.layout() triggers the navigation to disappear unexpectedly!? |  | ||||||
|             //this.table.layout(true, true); |  | ||||||
|             //this.composite.layout(true, true); |  | ||||||
|         } catch (final Exception e) { |         } catch (final Exception e) { | ||||||
|             log.warn("Failed to adaptColumnWidth: ", e); |             log.warn("Failed to adaptColumnWidth: ", e); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  | @ -30,7 +30,7 @@ import ch.ethz.seb.sebserver.gbl.Constants; | ||||||
| import ch.ethz.seb.sebserver.gbl.model.Entity; | import ch.ethz.seb.sebserver.gbl.model.Entity; | ||||||
| import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey; | import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey; | ||||||
| import ch.ethz.seb.sebserver.gui.table.ColumnDefinition.TableFilterAttribute; | import ch.ethz.seb.sebserver.gui.table.ColumnDefinition.TableFilterAttribute; | ||||||
| import ch.ethz.seb.sebserver.gui.widget.SingleSelection; | import ch.ethz.seb.sebserver.gui.widget.Selection; | ||||||
| import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.ImageIcon; | import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.ImageIcon; | ||||||
| 
 | 
 | ||||||
| public class TableFilter<ROW extends Entity> { | public class TableFilter<ROW extends Entity> { | ||||||
|  | @ -105,7 +105,7 @@ public class TableFilter<ROW extends Entity> { | ||||||
|             case TEXT: |             case TEXT: | ||||||
|                 return new TextFilter(attribute); |                 return new TextFilter(attribute); | ||||||
|             case SINGLE_SELECTION: |             case SINGLE_SELECTION: | ||||||
|                 return new Selection(attribute); |                 return new SelectionFilter(attribute); | ||||||
|             case DATE: |             case DATE: | ||||||
|                 return new Date(attribute); |                 return new Date(attribute); | ||||||
|             default: |             default: | ||||||
|  | @ -245,21 +245,24 @@ public class TableFilter<ROW extends Entity> { | ||||||
| 
 | 
 | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private class Selection extends FilterComponent { |     private class SelectionFilter extends FilterComponent { | ||||||
| 
 | 
 | ||||||
|         protected SingleSelection selector; |         protected Selection selector; | ||||||
| 
 | 
 | ||||||
|         Selection(final TableFilterAttribute attribute) { |         SelectionFilter(final TableFilterAttribute attribute) { | ||||||
|             super(attribute); |             super(attribute); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         @Override |         @Override | ||||||
|         FilterComponent build(final Composite parent) { |         FilterComponent build(final Composite parent) { | ||||||
|             this.selector = TableFilter.this.entityTable.widgetFactory |             this.selector = TableFilter.this.entityTable.widgetFactory | ||||||
|                     .singleSelectionLocalized( |                     .selectionLocalized( | ||||||
|  |                             ch.ethz.seb.sebserver.gui.widget.Selection.Type.SINGLE, | ||||||
|                             parent, |                             parent, | ||||||
|                             this.attribute.resourceSupplier); |                             this.attribute.resourceSupplier); | ||||||
|             this.selector.setLayoutData(this.rowData); |             this.selector | ||||||
|  |                     .adaptToControl() | ||||||
|  |                     .setLayoutData(this.rowData); | ||||||
|             return this; |             return this; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -34,7 +34,7 @@ public class MultiSelection extends Composite implements Selection { | ||||||
|     private final List<Label> labels = new ArrayList<>(); |     private final List<Label> labels = new ArrayList<>(); | ||||||
|     private final List<Label> selected = new ArrayList<>(); |     private final List<Label> selected = new ArrayList<>(); | ||||||
| 
 | 
 | ||||||
|     public MultiSelection(final Composite parent) { |     MultiSelection(final Composite parent) { | ||||||
|         super(parent, SWT.NONE); |         super(parent, SWT.NONE); | ||||||
|         final GridLayout gridLayout = new GridLayout(1, true); |         final GridLayout gridLayout = new GridLayout(1, true); | ||||||
|         gridLayout.verticalSpacing = 1; |         gridLayout.verticalSpacing = 1; | ||||||
|  | @ -44,6 +44,11 @@ public class MultiSelection extends Composite implements Selection { | ||||||
|         setLayout(gridLayout); |         setLayout(gridLayout); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     @Override | ||||||
|  |     public Type type() { | ||||||
|  |         return Type.MULTI; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     @Override |     @Override | ||||||
|     public void applyNewMapping(final List<Tuple<String>> mapping) { |     public void applyNewMapping(final List<Tuple<String>> mapping) { | ||||||
|         final String selectionValue = getSelectionValue(); |         final String selectionValue = getSelectionValue(); | ||||||
|  | @ -134,10 +139,4 @@ public class MultiSelection extends Composite implements Selection { | ||||||
|         deselectAll(); |         deselectAll(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @SuppressWarnings("unchecked") |  | ||||||
|     @Override |  | ||||||
|     public MultiSelection getTypeInstance() { |  | ||||||
|         return this; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -0,0 +1,131 @@ | ||||||
|  | /* | ||||||
|  |  * 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.widget; | ||||||
|  | 
 | ||||||
|  | import java.util.ArrayList; | ||||||
|  | import java.util.List; | ||||||
|  | import java.util.stream.Collectors; | ||||||
|  | 
 | ||||||
|  | import org.eclipse.swt.SWT; | ||||||
|  | import org.eclipse.swt.custom.TableEditor; | ||||||
|  | import org.eclipse.swt.layout.GridLayout; | ||||||
|  | import org.eclipse.swt.widgets.Combo; | ||||||
|  | import org.eclipse.swt.widgets.Composite; | ||||||
|  | import org.eclipse.swt.widgets.Event; | ||||||
|  | import org.eclipse.swt.widgets.Table; | ||||||
|  | import org.eclipse.swt.widgets.TableColumn; | ||||||
|  | import org.eclipse.swt.widgets.TableItem; | ||||||
|  | import org.slf4j.Logger; | ||||||
|  | import org.slf4j.LoggerFactory; | ||||||
|  | 
 | ||||||
|  | import ch.ethz.seb.sebserver.gbl.Constants; | ||||||
|  | import ch.ethz.seb.sebserver.gbl.util.Tuple; | ||||||
|  | import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey; | ||||||
|  | import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.ImageIcon; | ||||||
|  | 
 | ||||||
|  | public class MultiSelectionCombo extends Composite implements Selection { | ||||||
|  | 
 | ||||||
|  |     private static final Logger log = LoggerFactory.getLogger(MultiSelectionCombo.class); | ||||||
|  |     private static final long serialVersionUID = -7787134114963647332L; | ||||||
|  |     private static final int ACTION_COLUMN_WIDTH = 20; | ||||||
|  | 
 | ||||||
|  |     private final WidgetFactory widgetFactory; | ||||||
|  |     private final Table table; | ||||||
|  |     private final Combo combo; | ||||||
|  |     private final List<String> selected = new ArrayList<>(); | ||||||
|  |     private final List<Tuple<String>> mapping = new ArrayList<>(); | ||||||
|  | 
 | ||||||
|  |     MultiSelectionCombo(final Composite parent, final WidgetFactory widgetFactory) { | ||||||
|  |         super(parent, SWT.NONE); | ||||||
|  |         this.widgetFactory = widgetFactory; | ||||||
|  |         final GridLayout gridLayout = new GridLayout(1, true); | ||||||
|  |         gridLayout.verticalSpacing = 1; | ||||||
|  |         gridLayout.marginLeft = 0; | ||||||
|  |         gridLayout.marginHeight = 0; | ||||||
|  |         gridLayout.marginWidth = 0; | ||||||
|  |         setLayout(gridLayout); | ||||||
|  | 
 | ||||||
|  |         this.table = new Table(this, SWT.NONE); | ||||||
|  | 
 | ||||||
|  |         TableColumn column = new TableColumn(this.table, SWT.NONE); | ||||||
|  |         column = new TableColumn(this.table, SWT.NONE); | ||||||
|  |         column.setWidth(ACTION_COLUMN_WIDTH); | ||||||
|  |         this.table.setHeaderVisible(false); | ||||||
|  |         this.table.addListener(SWT.Resize, this::adaptColumnWidth); | ||||||
|  | 
 | ||||||
|  |         final TableItem header = new TableItem(this.table, SWT.NONE); | ||||||
|  |         final TableEditor editor = new TableEditor(this.table); | ||||||
|  |         this.combo = new Combo(this.table, SWT.NONE); | ||||||
|  |         editor.grabHorizontal = true; | ||||||
|  |         editor.setEditor(this.combo, header, 0); | ||||||
|  | 
 | ||||||
|  |         widgetFactory.imageButton( | ||||||
|  |                 ImageIcon.ADD, | ||||||
|  |                 this.table, | ||||||
|  |                 new LocTextKey("Add"), | ||||||
|  |                 this::addComboSelection); | ||||||
|  | 
 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public Type type() { | ||||||
|  |         return Type.MULTI_COMBO; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public void applyNewMapping(final List<Tuple<String>> mapping) { | ||||||
|  |         this.selected.clear(); | ||||||
|  |         this.mapping.clear(); | ||||||
|  |         this.mapping.addAll(mapping); | ||||||
|  | 
 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public void select(final String keys) { | ||||||
|  |         clear(); | ||||||
|  | 
 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public String getSelectionValue() { | ||||||
|  |         if (this.selected.isEmpty()) { | ||||||
|  |             return null; | ||||||
|  |         } | ||||||
|  |         return this.mapping | ||||||
|  |                 .stream() | ||||||
|  |                 .filter(t -> this.selected.contains(t._2)) | ||||||
|  |                 .map(t -> t._1) | ||||||
|  |                 .reduce("", (s1, s2) -> s1.concat(Constants.LIST_SEPARATOR).concat(s2)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public void clear() { | ||||||
|  |         this.selected.clear(); | ||||||
|  |         this.table.remove(1, this.table.getItemCount()); | ||||||
|  |         final List<String> names = this.mapping | ||||||
|  |                 .stream() | ||||||
|  |                 .map(t -> t._2) | ||||||
|  |                 .collect(Collectors.toList()); | ||||||
|  |         this.combo.setItems(names.toArray(new String[names.size()])); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private void addComboSelection(final Event event) { | ||||||
|  | 
 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private void adaptColumnWidth(final Event event) { | ||||||
|  |         try { | ||||||
|  |             final int currentTableWidth = this.table.getParent().getClientArea().width; | ||||||
|  |             this.table.getColumn(0).setWidth(currentTableWidth - ACTION_COLUMN_WIDTH); | ||||||
|  |         } catch (final Exception e) { | ||||||
|  |             log.warn("Failed to adaptColumnWidth: ", e); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | @ -10,10 +10,20 @@ package ch.ethz.seb.sebserver.gui.widget; | ||||||
| 
 | 
 | ||||||
| import java.util.List; | import java.util.List; | ||||||
| 
 | 
 | ||||||
|  | import org.eclipse.swt.widgets.Control; | ||||||
|  | 
 | ||||||
| import ch.ethz.seb.sebserver.gbl.util.Tuple; | import ch.ethz.seb.sebserver.gbl.util.Tuple; | ||||||
| 
 | 
 | ||||||
| public interface Selection { | public interface Selection { | ||||||
| 
 | 
 | ||||||
|  |     enum Type { | ||||||
|  |         SINGLE, | ||||||
|  |         MULTI, | ||||||
|  |         MULTI_COMBO | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     Type type(); | ||||||
|  | 
 | ||||||
|     void applyNewMapping(final List<Tuple<String>> mapping); |     void applyNewMapping(final List<Tuple<String>> mapping); | ||||||
| 
 | 
 | ||||||
|     void select(final String keys); |     void select(final String keys); | ||||||
|  | @ -24,6 +34,10 @@ public interface Selection { | ||||||
| 
 | 
 | ||||||
|     void setVisible(boolean visible); |     void setVisible(boolean visible); | ||||||
| 
 | 
 | ||||||
|     <T extends Selection> T getTypeInstance(); |     default Control adaptToControl() { | ||||||
|  |         return (Control) this; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     //<T extends Selection> T getTypeInstance(); | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -25,7 +25,7 @@ public class SingleSelection extends Combo implements Selection { | ||||||
|     final List<String> valueMapping; |     final List<String> valueMapping; | ||||||
|     final List<String> keyMapping; |     final List<String> keyMapping; | ||||||
| 
 | 
 | ||||||
|     public SingleSelection(final Composite parent) { |     SingleSelection(final Composite parent) { | ||||||
|         super(parent, SWT.READ_ONLY); |         super(parent, SWT.READ_ONLY); | ||||||
|         this.valueMapping = new ArrayList<>(); |         this.valueMapping = new ArrayList<>(); | ||||||
|         this.keyMapping = new ArrayList<>(); |         this.keyMapping = new ArrayList<>(); | ||||||
|  | @ -72,10 +72,9 @@ public class SingleSelection extends Combo implements Selection { | ||||||
|         super.setItems(this.valueMapping.toArray(new String[this.valueMapping.size()])); |         super.setItems(this.valueMapping.toArray(new String[this.valueMapping.size()])); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @SuppressWarnings("unchecked") |  | ||||||
|     @Override |     @Override | ||||||
|     public SingleSelection getTypeInstance() { |     public Type type() { | ||||||
|         return this; |         return Type.SINGLE; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -60,6 +60,8 @@ public class WidgetFactory { | ||||||
|     public enum ImageIcon { |     public enum ImageIcon { | ||||||
|         MAXIMIZE("maximize.png"), |         MAXIMIZE("maximize.png"), | ||||||
|         MINIMIZE("minimize.png"), |         MINIMIZE("minimize.png"), | ||||||
|  |         ADD("add.png"), | ||||||
|  |         REMOVE("remove.png"), | ||||||
|         EDIT("edit.png"), |         EDIT("edit.png"), | ||||||
|         TEST("test.png"), |         TEST("test.png"), | ||||||
|         IMPORT("import.png"), |         IMPORT("import.png"), | ||||||
|  | @ -307,28 +309,65 @@ public class WidgetFactory { | ||||||
|         return imageButton; |         return imageButton; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public SingleSelection singleSelectionLocalized( |     public Selection selectionLocalized( | ||||||
|  |             final Selection.Type type, | ||||||
|             final Composite parent, |             final Composite parent, | ||||||
|             final Supplier<List<Tuple<String>>> itemsSupplier) { |             final Supplier<List<Tuple<String>>> itemsSupplier) { | ||||||
| 
 | 
 | ||||||
|         final SingleSelection singleSelection = new SingleSelection(parent); |         final Selection selection; | ||||||
|         final Consumer<SingleSelection> updateFunction = ss -> ss.applyNewMapping(itemsSupplier.get()); |         switch (type) { | ||||||
|         singleSelection.setData(POLYGLOT_WIDGET_FUNCTION_KEY, updateFunction); |             case SINGLE: | ||||||
|         updateFunction.accept(singleSelection); |                 selection = new SingleSelection(parent); | ||||||
|         return singleSelection; |                 break; | ||||||
|  |             case MULTI: | ||||||
|  |                 selection = new MultiSelection(parent); | ||||||
|  |                 break; | ||||||
|  |             case MULTI_COMBO: | ||||||
|  |                 selection = new MultiSelectionCombo(parent, this); | ||||||
|  |                 break; | ||||||
|  |             default: | ||||||
|  |                 throw new IllegalArgumentException("Unsupported Selection.Type: " + type); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|     public MultiSelection multiSelectionLocalized( |         final Consumer<Selection> updateFunction = ss -> ss.applyNewMapping(itemsSupplier.get()); | ||||||
|             final Composite parent, |         selection.adaptToControl().setData(POLYGLOT_WIDGET_FUNCTION_KEY, updateFunction); | ||||||
|             final Supplier<List<Tuple<String>>> itemsSupplier) { |         updateFunction.accept(selection); | ||||||
| 
 |         return selection; | ||||||
|         final MultiSelection multiSelection = new MultiSelection(parent); |  | ||||||
|         final Consumer<MultiSelection> updateFunction = ss -> ss.applyNewMapping(itemsSupplier.get()); |  | ||||||
|         multiSelection.setData(POLYGLOT_WIDGET_FUNCTION_KEY, updateFunction); |  | ||||||
|         updateFunction.accept(multiSelection); |  | ||||||
|         return multiSelection; |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | //    public SingleSelection singleSelectionLocalized( | ||||||
|  | //            final Composite parent, | ||||||
|  | //            final Supplier<List<Tuple<String>>> itemsSupplier) { | ||||||
|  | // | ||||||
|  | //        final SingleSelection singleSelection = new SingleSelection(parent); | ||||||
|  | //        final Consumer<SingleSelection> updateFunction = ss -> ss.applyNewMapping(itemsSupplier.get()); | ||||||
|  | //        singleSelection.setData(POLYGLOT_WIDGET_FUNCTION_KEY, updateFunction); | ||||||
|  | //        updateFunction.accept(singleSelection); | ||||||
|  | //        return singleSelection; | ||||||
|  | //    } | ||||||
|  | // | ||||||
|  | //    public MultiSelection multiSelectionLocalized( | ||||||
|  | //            final Composite parent, | ||||||
|  | //            final Supplier<List<Tuple<String>>> itemsSupplier) { | ||||||
|  | // | ||||||
|  | //        final MultiSelection multiSelection = new MultiSelection(parent); | ||||||
|  | //        final Consumer<MultiSelection> updateFunction = ss -> ss.applyNewMapping(itemsSupplier.get()); | ||||||
|  | //        multiSelection.setData(POLYGLOT_WIDGET_FUNCTION_KEY, updateFunction); | ||||||
|  | //        updateFunction.accept(multiSelection); | ||||||
|  | //        return multiSelection; | ||||||
|  | //    } | ||||||
|  | // | ||||||
|  | //    public MultiSelectionCombo multiSelectionComboLocalized( | ||||||
|  | //            final Composite parent, | ||||||
|  | //            final Supplier<List<Tuple<String>>> itemsSupplier) { | ||||||
|  | // | ||||||
|  | //        final MultiSelectionCombo multiSelection = new MultiSelectionCombo(parent, this); | ||||||
|  | //        final Consumer<MultiSelectionCombo> updateFunction = ss -> ss.applyNewMapping(itemsSupplier.get()); | ||||||
|  | //        multiSelection.setData(POLYGLOT_WIDGET_FUNCTION_KEY, updateFunction); | ||||||
|  | //        updateFunction.accept(multiSelection); | ||||||
|  | //        return multiSelection; | ||||||
|  | //    } | ||||||
|  | 
 | ||||||
|     public ImageUpload imageUploadLocalized( |     public ImageUpload imageUploadLocalized( | ||||||
|             final Composite parent, |             final Composite parent, | ||||||
|             final LocTextKey locTextKey, |             final LocTextKey locTextKey, | ||||||
|  |  | ||||||
|  | @ -170,7 +170,7 @@ public class ClientCredentialServiceImpl implements ClientCredentialService { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private final static char[] possibleCharacters = (new String( |     private final static char[] possibleCharacters = (new String( | ||||||
|             "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789~`!@#$%^&*()-_=+[{]}?")) |             "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789~`!@#$%^*()-_=+[{]}?")) | ||||||
|                     .toCharArray(); |                     .toCharArray(); | ||||||
| 
 | 
 | ||||||
|     private CharSequence getSalt(final CharSequence saltPlain) throws UnsupportedEncodingException { |     private CharSequence getSalt(final CharSequence saltPlain) throws UnsupportedEncodingException { | ||||||
|  |  | ||||||
|  | @ -67,6 +67,10 @@ public class FilterMap extends POSTMapper { | ||||||
|         return getString(UserInfo.FILTER_ATTR_LANGUAGE); |         return getString(UserInfo.FILTER_ATTR_LANGUAGE); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     public String getUserRole() { | ||||||
|  |         return getString(UserInfo.FILTER_ATTR_ROLE); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     public String getLmsSetupType() { |     public String getLmsSetupType() { | ||||||
|         return getString(LmsSetup.FILTER_ATTR_LMS_TYPE); |         return getString(LmsSetup.FILTER_ATTR_LMS_TYPE); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -150,7 +150,13 @@ public class UserDAOImpl implements UserDAO { | ||||||
|     @Override |     @Override | ||||||
|     @Transactional(readOnly = true) |     @Transactional(readOnly = true) | ||||||
|     public Result<Collection<UserInfo>> allMatching(final FilterMap filterMap, final Predicate<UserInfo> predicate) { |     public Result<Collection<UserInfo>> allMatching(final FilterMap filterMap, final Predicate<UserInfo> predicate) { | ||||||
|         return Result.tryCatch(() -> this.userRecordMapper |         return Result.tryCatch(() -> { | ||||||
|  |             final String userRole = filterMap.getUserRole(); | ||||||
|  |             final Predicate<UserInfo> _predicate = (StringUtils.isNotBlank(userRole)) | ||||||
|  |                     ? predicate.and(ui -> ui.roles.contains(userRole)) | ||||||
|  |                     : predicate; | ||||||
|  | 
 | ||||||
|  |             return this.userRecordMapper | ||||||
|                     .selectByExample() |                     .selectByExample() | ||||||
|                     .where( |                     .where( | ||||||
|                             UserRecordDynamicSqlSupport.active, |                             UserRecordDynamicSqlSupport.active, | ||||||
|  | @ -175,8 +181,9 @@ public class UserDAOImpl implements UserDAO { | ||||||
|                     .stream() |                     .stream() | ||||||
|                     .map(this::toDomainModel) |                     .map(this::toDomainModel) | ||||||
|                     .flatMap(DAOLoggingSupport::logUnexpectedErrorAndSkip) |                     .flatMap(DAOLoggingSupport::logUnexpectedErrorAndSkip) | ||||||
|                 .filter(predicate) |                     .filter(_predicate) | ||||||
|                 .collect(Collectors.toList())); |                     .collect(Collectors.toList()); | ||||||
|  |         }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|  |  | ||||||
|  | @ -12,6 +12,8 @@ sebserver.overall.action.modify.cancel=Cancel | ||||||
| sebserver.overall.action.modify.cancel.confirm=Are you sure to cancel? Modifications will be lost. | sebserver.overall.action.modify.cancel.confirm=Are you sure to cancel? Modifications will be lost. | ||||||
| sebserver.overall.action.filter=Apply Filter | sebserver.overall.action.filter=Apply Filter | ||||||
| sebserver.overall.action.filter.clear=Clear Filter Criteria | sebserver.overall.action.filter.clear=Clear Filter Criteria | ||||||
|  | sebserver.overall.action.ok=OK | ||||||
|  | sebserver.overall.action.cancel=Cancel | ||||||
| 
 | 
 | ||||||
| sebserver.overall.action.category.varia=Varia | sebserver.overall.action.category.varia=Varia | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -96,7 +96,7 @@ Label.selected { | ||||||
| Label-SeparatorLine { | Label-SeparatorLine { | ||||||
|     background-image: none; |     background-image: none; | ||||||
|     background-color: transparent; |     background-color: transparent; | ||||||
|     border: 1px solid #595959; |     border: 1px solid #bdbdbd; | ||||||
|     border-radius: 0px; |     border-radius: 0px; | ||||||
|     height: 1px; |     height: 1px; | ||||||
| } | } | ||||||
|  |  | ||||||
							
								
								
									
										
											BIN
										
									
								
								src/main/resources/static/images/add.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/main/resources/static/images/add.png
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 98 B | 
							
								
								
									
										
											BIN
										
									
								
								src/main/resources/static/images/remove.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/main/resources/static/images/remove.png
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 85 B | 
		Loading…
	
	Add table
		
		Reference in a new issue
	
	 anhefti
						anhefti