diff --git a/scm-core/src/main/java/sonia/scm/DisplayManager.java b/scm-core/src/main/java/sonia/scm/DisplayManager.java new file mode 100644 index 0000000000..72113a483d --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/DisplayManager.java @@ -0,0 +1,19 @@ +package sonia.scm; + +import java.util.Collection; +import java.util.Optional; + +public interface DisplayManager { + + int DEFAULT_LIMIT = 5; + + /** + * Returns a {@link Collection} of filtered objects + * + * @param filter the searched string + * @return filtered object from the store + */ + Collection autocomplete(String filter); + + Optional get(String id); +} diff --git a/scm-core/src/main/java/sonia/scm/Manager.java b/scm-core/src/main/java/sonia/scm/Manager.java index 390777958d..9a7f21d3ef 100644 --- a/scm-core/src/main/java/sonia/scm/Manager.java +++ b/scm-core/src/main/java/sonia/scm/Manager.java @@ -47,8 +47,6 @@ public interface Manager extends HandlerBase, LastModifiedAware { - int DEFAULT_LIMIT = 5; - /** * Reloads a object from store and overwrites all changes. diff --git a/scm-core/src/main/java/sonia/scm/TransformFilter.java b/scm-core/src/main/java/sonia/scm/TransformFilter.java index e872ab384f..fa798fe36e 100644 --- a/scm-core/src/main/java/sonia/scm/TransformFilter.java +++ b/scm-core/src/main/java/sonia/scm/TransformFilter.java @@ -39,8 +39,10 @@ package sonia.scm; * @author Sebastian Sdorra * * @param type of objects to transform + * @param result type of the transformation */ -public interface TransformFilter +@FunctionalInterface +public interface TransformFilter { /** @@ -52,5 +54,5 @@ public interface TransformFilter * * @return tranformed object */ - public T accept(T item); + R accept(T item); } diff --git a/scm-core/src/main/java/sonia/scm/group/DisplayGroup.java b/scm-core/src/main/java/sonia/scm/group/DisplayGroup.java new file mode 100644 index 0000000000..a16a3046e9 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/group/DisplayGroup.java @@ -0,0 +1,26 @@ +package sonia.scm.group; + +import sonia.scm.ReducedModelObject; + +public class DisplayGroup implements ReducedModelObject { + + private final String id; + private final String displayName; + + public static DisplayGroup from(Group group) { + return new DisplayGroup(group.getId(), group.getDescription()); + } + + private DisplayGroup(String id, String displayName) { + this.id = id; + this.displayName = displayName; + } + + public String getId() { + return id; + } + + public String getDisplayName() { + return displayName; + } +} diff --git a/scm-core/src/main/java/sonia/scm/group/GroupDisplayManager.java b/scm-core/src/main/java/sonia/scm/group/GroupDisplayManager.java new file mode 100644 index 0000000000..d9188e9402 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/group/GroupDisplayManager.java @@ -0,0 +1,6 @@ +package sonia.scm.group; + +import sonia.scm.DisplayManager; + +public interface GroupDisplayManager extends DisplayManager { +} diff --git a/scm-core/src/main/java/sonia/scm/group/GroupManager.java b/scm-core/src/main/java/sonia/scm/group/GroupManager.java index 08057ae3db..288196894d 100644 --- a/scm-core/src/main/java/sonia/scm/group/GroupManager.java +++ b/scm-core/src/main/java/sonia/scm/group/GroupManager.java @@ -61,14 +61,4 @@ public interface GroupManager * @return all groups assigned to the given member */ public Collection getGroupsForMember(String member); - - - /** - * Returns a {@link java.util.Collection} of filtered objects - * - * @param filter the searched string - * @return filtered object from the store - */ - Collection autocomplete(String filter); - } diff --git a/scm-core/src/main/java/sonia/scm/group/GroupManagerDecorator.java b/scm-core/src/main/java/sonia/scm/group/GroupManagerDecorator.java index ef6de4164c..e2367d863c 100644 --- a/scm-core/src/main/java/sonia/scm/group/GroupManagerDecorator.java +++ b/scm-core/src/main/java/sonia/scm/group/GroupManagerDecorator.java @@ -109,11 +109,6 @@ public class GroupManagerDecorator return decorated.getGroupsForMember(member); } - @Override - public Collection autocomplete(String filter) { - return decorated.autocomplete(filter); - } - //~--- fields --------------------------------------------------------------- /** Field description */ diff --git a/scm-core/src/main/java/sonia/scm/search/SearchUtil.java b/scm-core/src/main/java/sonia/scm/search/SearchUtil.java index 90c65e3456..281e00a517 100644 --- a/scm-core/src/main/java/sonia/scm/search/SearchUtil.java +++ b/scm-core/src/main/java/sonia/scm/search/SearchUtil.java @@ -159,17 +159,17 @@ public final class SearchUtil * * @return */ - public static Collection search(SearchRequest searchRequest, - Collection collection, TransformFilter filter) + public static Collection search(SearchRequest searchRequest, + Collection collection, TransformFilter filter) { - List items = new ArrayList(); + List items = new ArrayList<>(); int index = 0; int counter = 0; Iterator it = collection.iterator(); while (it.hasNext()) { - T item = filter.accept(it.next()); + R item = filter.accept(it.next()); if (item != null) { diff --git a/scm-core/src/main/java/sonia/scm/template/TemplateEngine.java b/scm-core/src/main/java/sonia/scm/template/TemplateEngine.java index bfeca8f42c..0f26fd49c4 100644 --- a/scm-core/src/main/java/sonia/scm/template/TemplateEngine.java +++ b/scm-core/src/main/java/sonia/scm/template/TemplateEngine.java @@ -37,6 +37,7 @@ package sonia.scm.template; import java.io.IOException; import java.io.Reader; +import java.util.Locale; /** * The {@link TemplateEngine} searches for {@link Template}s and prepares the @@ -59,11 +60,42 @@ public interface TemplateEngine * * @param templatePath path of the template * - * @return template associated withe the given path or null + * @return template associated with the given path or null * * @throws IOException */ - public Template getTemplate(String templatePath) throws IOException; + Template getTemplate(String templatePath) throws IOException; + + /** + * Returns the template associated with the given path and the given language + * or the default version. To do this, the template path will be extended + * with the language. For example `my-template.type` will be extended to + * `my-template_en.type`, if the language is {@link Locale#ENGLISH}. If no + * dedicated template can be found, the template without the extension will + * be used. + *

The template engine + * will search the template in the folder of the web application and in + * the classpath. This method will return null, + * if no template could be found for the given path. + * + * + * @param templatePath path of the template + * @param locale the preferred language + * + * @return template associated with the given path, extended by the language + * if found, or null + * + * @throws IOException + */ + default Template getTemplate(String templatePath, Locale locale) throws IOException { + String templatePathWithLanguage = extendWithLanguage(templatePath, locale); + Template templateForLanguage = getTemplate(templatePathWithLanguage); + if (templateForLanguage == null) { + return getTemplate(templatePath); + } else { + return templateForLanguage; + } + } /** * Creates a template of the given reader. Note some template implementations @@ -79,7 +111,7 @@ public interface TemplateEngine * * @since 1.22 */ - public Template getTemplate(String templateIdentifier, Reader reader) + Template getTemplate(String templateIdentifier, Reader reader) throws IOException; /** @@ -87,5 +119,12 @@ public interface TemplateEngine * * @return type of template engine */ - public TemplateType getType(); + TemplateType getType(); + + static String extendWithLanguage(String templatePath, Locale locale) { + int lastIndexOfDot = templatePath.lastIndexOf('.'); + String filename = templatePath.substring(0, lastIndexOfDot); + String extension = templatePath.substring(lastIndexOfDot); + return filename + "_" + locale.getLanguage() + extension; + } } diff --git a/scm-core/src/main/java/sonia/scm/user/DisplayUser.java b/scm-core/src/main/java/sonia/scm/user/DisplayUser.java new file mode 100644 index 0000000000..7a11dfbab4 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/user/DisplayUser.java @@ -0,0 +1,32 @@ +package sonia.scm.user; + +import sonia.scm.ReducedModelObject; + +public class DisplayUser implements ReducedModelObject { + + private final String id; + private final String displayName; + private final String mail; + + public static DisplayUser from(User user) { + return new DisplayUser(user.getId(), user.getDisplayName(), user.getMail()); + } + + private DisplayUser(String id, String displayName, String mail) { + this.id = id; + this.displayName = displayName; + this.mail = mail; + } + + public String getId() { + return id; + } + + public String getDisplayName() { + return displayName; + } + + public String getMail() { + return mail; + } +} diff --git a/scm-core/src/main/java/sonia/scm/user/UserDisplayManager.java b/scm-core/src/main/java/sonia/scm/user/UserDisplayManager.java new file mode 100644 index 0000000000..159025f5ec --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/user/UserDisplayManager.java @@ -0,0 +1,6 @@ +package sonia.scm.user; + +import sonia.scm.DisplayManager; + +public interface UserDisplayManager extends DisplayManager { +} diff --git a/scm-core/src/main/java/sonia/scm/user/UserManager.java b/scm-core/src/main/java/sonia/scm/user/UserManager.java index f301c1f2b1..a735918c59 100644 --- a/scm-core/src/main/java/sonia/scm/user/UserManager.java +++ b/scm-core/src/main/java/sonia/scm/user/UserManager.java @@ -75,14 +75,6 @@ public interface UserManager return getDefaultType().equals(user.getType()); } - /** - * Returns a {@link java.util.Collection} of filtered objects - * - * @param filter the searched string - * @return filtered object from the store - */ - Collection autocomplete(String filter); - /** * Changes the password of the logged in user. * @param oldPassword The current encrypted password of the user. diff --git a/scm-core/src/main/java/sonia/scm/user/UserManagerDecorator.java b/scm-core/src/main/java/sonia/scm/user/UserManagerDecorator.java index 0384fe1b52..0b88d856ff 100644 --- a/scm-core/src/main/java/sonia/scm/user/UserManagerDecorator.java +++ b/scm-core/src/main/java/sonia/scm/user/UserManagerDecorator.java @@ -121,11 +121,6 @@ public class UserManagerDecorator extends ManagerDecorator return decorated.getDefaultType(); } - @Override - public Collection autocomplete(String filter) { - return decorated.autocomplete(filter); - } - @Override public void changePasswordForLoggedInUser(String oldPassword, String newPassword) { decorated.changePasswordForLoggedInUser(oldPassword, newPassword); diff --git a/scm-ui-components/packages/ui-components/src/Autocomplete.js b/scm-ui-components/packages/ui-components/src/Autocomplete.js index f3023e268b..adf86e37b7 100644 --- a/scm-ui-components/packages/ui-components/src/Autocomplete.js +++ b/scm-ui-components/packages/ui-components/src/Autocomplete.js @@ -1,6 +1,6 @@ // @flow import React from "react"; -import { AsyncCreatable } from "react-select"; +import { AsyncCreatable, Async } from "react-select"; import type { AutocompleteObject, SelectValue } from "@scm-manager/ui-types"; import LabelWithHelpIcon from "./forms/LabelWithHelpIcon"; @@ -13,7 +13,8 @@ type Props = { value?: SelectValue, placeholder: string, loadingMessage: string, - noOptionsMessage: string + noOptionsMessage: string, + creatable?: boolean }; @@ -42,27 +43,40 @@ class Autocomplete extends React.Component { }; render() { - const { label, helpText, value, placeholder, loadingMessage, noOptionsMessage, loadSuggestions } = this.props; + const { label, helpText, value, placeholder, loadingMessage, noOptionsMessage, loadSuggestions, creatable } = this.props; return (

- loadingMessage} - noOptionsMessage={() => noOptionsMessage} - isValidNewOption={this.isValidNewOption} - onCreateOption={value => { - this.handleInputChange({ - label: value, - value: { id: value, displayName: value } - }); - }} - /> + {creatable? + loadingMessage} + noOptionsMessage={() => noOptionsMessage} + isValidNewOption={this.isValidNewOption} + onCreateOption={value => { + this.handleInputChange({ + label: value, + value: { id: value, displayName: value } + }); + }} + /> + : + loadingMessage} + noOptionsMessage={() => noOptionsMessage} + /> + + }
); diff --git a/scm-ui-components/packages/ui-components/src/forms/DropDown.js b/scm-ui-components/packages/ui-components/src/forms/DropDown.js index 62a7f1ebe1..457039a0a8 100644 --- a/scm-ui-components/packages/ui-components/src/forms/DropDown.js +++ b/scm-ui-components/packages/ui-components/src/forms/DropDown.js @@ -5,6 +5,7 @@ import classNames from "classnames"; type Props = { options: string[], + optionValues?: string[], optionSelected: string => void, preselectedOption?: string, className: any, @@ -13,7 +14,7 @@ type Props = { class DropDown extends React.Component { render() { - const { options, preselectedOption, className, disabled } = this.props; + const { options, optionValues, preselectedOption, className, disabled } = this.props; return (