diff --git a/.classpath b/.classpath new file mode 100644 index 0000000..0b062bc --- /dev/null +++ b/.classpath @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.project b/.project new file mode 100644 index 0000000..42b10c5 --- /dev/null +++ b/.project @@ -0,0 +1,49 @@ + + + message-conversations + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.wst.common.project.facet.core.builder + + + + + org.eclipse.wst.validation.validationbuilder + + + + + com.google.gdt.eclipse.core.webAppProjectValidator + + + + + com.google.gwt.eclipse.core.gwtProjectValidator + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.jem.workbench.JavaEMFNature + org.eclipse.wst.common.modulecore.ModuleCoreNature + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + org.eclipse.wst.common.project.facet.core.nature + org.eclipse.wst.jsdt.core.jsNature + com.liferay.ide.core.liferayNature + com.google.gwt.eclipse.core.gwtNature + + diff --git a/.settings/.jsdtscope b/.settings/.jsdtscope new file mode 100644 index 0000000..f179e11 --- /dev/null +++ b/.settings/.jsdtscope @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/.settings/com.google.gdt.eclipse.core.prefs b/.settings/com.google.gdt.eclipse.core.prefs new file mode 100644 index 0000000..d890b4f --- /dev/null +++ b/.settings/com.google.gdt.eclipse.core.prefs @@ -0,0 +1,4 @@ +eclipse.preferences.version=1 +lastWarOutDir=/Users/massi/Documents/workspace/message-conversations/target/message-conversations-0.0.1-SNAPSHOT +warSrcDir=src/main/webapp +warSrcDirIsOutput=false diff --git a/.settings/com.google.gwt.eclipse.core.prefs b/.settings/com.google.gwt.eclipse.core.prefs new file mode 100644 index 0000000..fb32c70 --- /dev/null +++ b/.settings/com.google.gwt.eclipse.core.prefs @@ -0,0 +1,3 @@ +eclipse.preferences.version=1 +entryPointModules= +filesCopiedToWebInfLib= diff --git a/.settings/com.gwtplugins.gdt.eclipse.core.prefs b/.settings/com.gwtplugins.gdt.eclipse.core.prefs new file mode 100644 index 0000000..9a193e3 --- /dev/null +++ b/.settings/com.gwtplugins.gdt.eclipse.core.prefs @@ -0,0 +1,3 @@ +eclipse.preferences.version=1 +warSrcDir=src/main/webapp +warSrcDirIsOutput=false diff --git a/.settings/com.gwtplugins.gwt.eclipse.core.prefs b/.settings/com.gwtplugins.gwt.eclipse.core.prefs new file mode 100644 index 0000000..fb32c70 --- /dev/null +++ b/.settings/com.gwtplugins.gwt.eclipse.core.prefs @@ -0,0 +1,3 @@ +eclipse.preferences.version=1 +entryPointModules= +filesCopiedToWebInfLib= diff --git a/.settings/org.eclipse.core.resources.prefs b/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 0000000..0bf7f20 --- /dev/null +++ b/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,5 @@ +eclipse.preferences.version=1 +encoding//src/main/java=UTF-8 +encoding//src/main/resources=UTF-8 +encoding//target/generated-sources/gwt=UTF-8 +encoding/=UTF-8 diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..6e80039 --- /dev/null +++ b/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,8 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 +org.eclipse.jdt.core.compiler.compliance=1.8 +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning +org.eclipse.jdt.core.compiler.source=1.8 diff --git a/.settings/org.eclipse.jst.jsp.core.prefs b/.settings/org.eclipse.jst.jsp.core.prefs new file mode 100644 index 0000000..3a5c98d --- /dev/null +++ b/.settings/org.eclipse.jst.jsp.core.prefs @@ -0,0 +1,3 @@ +eclipse.preferences.version=1 +validateFragments=false +validation.use-project-settings=true diff --git a/.settings/org.eclipse.m2e.core.prefs b/.settings/org.eclipse.m2e.core.prefs new file mode 100644 index 0000000..f897a7f --- /dev/null +++ b/.settings/org.eclipse.m2e.core.prefs @@ -0,0 +1,4 @@ +activeProfiles= +eclipse.preferences.version=1 +resolveWorkspaceProjects=true +version=1 diff --git a/.settings/org.eclipse.wst.common.component b/.settings/org.eclipse.wst.common.component new file mode 100644 index 0000000..f48cf24 --- /dev/null +++ b/.settings/org.eclipse.wst.common.component @@ -0,0 +1,14 @@ + + + + + + + + + uses + + + + + diff --git a/.settings/org.eclipse.wst.common.project.facet.core.xml b/.settings/org.eclipse.wst.common.project.facet.core.xml new file mode 100644 index 0000000..40d1d27 --- /dev/null +++ b/.settings/org.eclipse.wst.common.project.facet.core.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/.settings/org.eclipse.wst.jsdt.ui.superType.container b/.settings/org.eclipse.wst.jsdt.ui.superType.container new file mode 100644 index 0000000..3bd5d0a --- /dev/null +++ b/.settings/org.eclipse.wst.jsdt.ui.superType.container @@ -0,0 +1 @@ +org.eclipse.wst.jsdt.launching.baseBrowserLibrary \ No newline at end of file diff --git a/.settings/org.eclipse.wst.jsdt.ui.superType.name b/.settings/org.eclipse.wst.jsdt.ui.superType.name new file mode 100644 index 0000000..05bd71b --- /dev/null +++ b/.settings/org.eclipse.wst.jsdt.ui.superType.name @@ -0,0 +1 @@ +Window \ No newline at end of file diff --git a/.settings/org.eclipse.wst.validation.prefs b/.settings/org.eclipse.wst.validation.prefs new file mode 100644 index 0000000..04cad8c --- /dev/null +++ b/.settings/org.eclipse.wst.validation.prefs @@ -0,0 +1,2 @@ +disabled=06target +eclipse.preferences.version=1 diff --git a/.tern-project b/.tern-project new file mode 100644 index 0000000..d7fbf24 --- /dev/null +++ b/.tern-project @@ -0,0 +1,23 @@ +{ + "plugins": { + "guess-types": { + + }, + "outline": { + + }, + "liferay": { + + }, + "yui3": { + + }, + "aui2.0.x": { + + } + }, + "libs": [ + "ecma5", + "browser" + ] +} \ No newline at end of file diff --git a/distro/LICENSE b/distro/LICENSE new file mode 100644 index 0000000..2d9616a --- /dev/null +++ b/distro/LICENSE @@ -0,0 +1 @@ +${gcube.license} \ No newline at end of file diff --git a/distro/README b/distro/README new file mode 100644 index 0000000..619958c --- /dev/null +++ b/distro/README @@ -0,0 +1,61 @@ +The gCube System - ${name} +-------------------------------------------------- + +${description} + + +${gcube.description} + +${gcube.funding} + + +Version +-------------------------------------------------- + +${version} (${buildDate}) + +Please see the file named "changelog.xml" in this directory for the release notes. + + +Authors +-------------------------------------------------- + +* Massimiliano Assante (massimiliano.assante-AT-isti.cnr.it), Istituto di Scienza e Tecnologie dell'Informazione "A. Faedo" - CNR, Pisa (Italy). + +Maintainers +----------- + +* Francesco Mangiacrapa (francesco.mangiacrapa-AT-isti.cnr.it), Istituto di Scienza e Tecnologie dell'Informazione "A. Faedo" - CNR, Pisa (Italy). +* Massimiliano Assante (massimiliano.assante-AT-isti.cnr.it), Istituto di Scienza e Tecnologie dell'Informazione "A. Faedo" - CNR, Pisa (Italy). + +Download information +-------------------------------------------------- + +Source code is available from SVN: + ${scm.url} + +Binaries can be downloaded from the gCube website: + ${gcube.website} + + +Installation +-------------------------------------------------- + + +Documentation +-------------------------------------------------- + +Documentation is available on-line in the gCube Wiki: + ${gcube.wikiRoot}/Workspace + +Support +-------------------------------------------------- + +Bugs and support requests can be reported in the gCube issue tracking tool: + ${gcube.issueTracking} + + +Licensing +-------------------------------------------------- + +This software is licensed under the terms you may find in the file named "LICENSE" in this directory. \ No newline at end of file diff --git a/distro/changelog.xml b/distro/changelog.xml new file mode 100644 index 0000000..dc49813 --- /dev/null +++ b/distro/changelog.xml @@ -0,0 +1,50 @@ + + + Improved look and feel + Responsive, works on mobile + + + Removed ASL Session + + + [Feature #879] Message Improvements + + + Upgraded to GWT 2.7 + [Feature #129] Porting to HL 2.0 + + + Fixed bug on "reply all" + Changed icons + + + Updated pom to support new gcube core (gcube release 3.2) + + + + #Ticket 2223. This project was enhancements to gwt 2.5.1 + + GCF dependency was removed + + + Updated show attachments: this functionality now calls the + servlet contained in ws-tree + + Error management for attachments: it was added redirect + functionality in download servlet + + Fixed Ticket #1348 + + + First Release + + diff --git a/distro/descriptor.xml b/distro/descriptor.xml new file mode 100644 index 0000000..c597cd2 --- /dev/null +++ b/distro/descriptor.xml @@ -0,0 +1,32 @@ + + servicearchive + + tar.gz + + / + + + ${distroDirectory} + / + true + + README + LICENSE + changelog.xml + profile.xml + + 755 + true + + + + + target/${build.finalName}.${project.packaging} + /${artifactId} + + + + diff --git a/distro/profile.xml b/distro/profile.xml new file mode 100644 index 0000000..51c3b6b --- /dev/null +++ b/distro/profile.xml @@ -0,0 +1,25 @@ + + + + Service + + ${description} + PortletUser + ${artifactId} + ${version} + + + ${artifactId} + ${version} + + ${groupId} + ${artifactId} + ${version} + + + target/${build.finalName}.war + + + + + diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..755cd00 --- /dev/null +++ b/pom.xml @@ -0,0 +1,308 @@ + + + + 4.0.0 + + maven-parent + org.gcube.tools + 1.0.0 + + + org.gcube.portets.user + messages + war + 2.0.0-SNAPSHOT + + gCube Messages Portlet for exchanging messages with other users. + + + scm:svn:http://svn.d4science.research-infrastructures.eu/gcube/trunk/portlets/user/message-conversations + scm:https://svn.d4science.research-infrastructures.eu/gcube/trunk/portlets/user/message-conversations + http://svn.d4science.research-infrastructures.eu/gcube/trunk/portlets/user/message-conversations + + + + + 2.8.1 + distro + + 1.8 + 1.8 + 2.0-rc6 + UTF-8 + + + + + + org.gcube.distribution + maven-portal-bom + LATEST + pom + import + + + com.google.gwt + gwt + ${gwt.version} + pom + import + + + + + + + + + org.gcube.portlets.widgets + workspace-explorer + [1.8.0-SNAPSHOT,2.0.0-SNAPSHOT) + + + org.gcube.applicationsupportlayer + aslsocial + provided + + + org.gcube.common.portal + portal-manager + provided + + + org.gcube.portal + client-context-library + [1.0.0-SNAPSHOT,) + compile + + + javax.portlet + portlet-api + provided + + + com.google.gwt + gwt-servlet + ${gwt.version} + provided + + + com.google.gwt + gwt-user + ${gwt.version} + provided + + + com.google.gwt + gwt-dev + ${gwt.version} + provided + + + + org.gcube.core + common-scope-maps + compile + + + + com.github.gwtmaterialdesign + gwt-material + ${gwt-material.version} + + + com.github.gwtmaterialdesign + gwt-material-themes + ${gwt-material.version} + + + com.github.gwtmaterialdesign + gwt-material-addins + ${gwt-material.version} + + + org.gcube.portal + notifications-common-library + provided + + + + org.gcube.common + home-library-jcr + provided + + + org.gcube.common + home-library-model + provided + + + org.gcube.common + home-library + provided + + + asm-all + asm + + + + + xml-apis + xml-apis + 1.4.01 + + + javax.servlet + javax.servlet-api + + provided + + + org.gcube.dvos + usermanagement-core + provided + + + org.gcube.common + authorization-client + provided + + + org.gcube.common + common-authorization + provided + + + com.liferay.portal + portal-service + provided + + + com.liferay.portal + util-java + provided + + + junit + junit + 4.11 + test + + + + + + ${project.build.directory}/${project.build.finalName}/WEB-INF/classes + + + + + + org.codehaus.mojo + gwt-maven-plugin + 2.8.1 + + + + compile + generateAsync + + + + + + MessageConversations.html + + org.gcube.portets.user.message_conversations.MessageConversations + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.0.0-M1 + + -Xdoclint:none + + + + + org.apache.maven.plugins + maven-war-plugin + 2.1.1 + + + compile + + exploded + + + + + + ${webappDirectory} + + + + org.apache.maven.plugins + maven-compiler-plugin + 2.3.2 + + ${maven.compiler.source} + ${maven.compiler.target} + + + + + org.apache.maven.plugins + maven-assembly-plugin + 2.2 + + + ${distroDirectory}/descriptor.xml + + + + + servicearchive + install + + single + + + + + + org.apache.maven.plugins + maven-resources-plugin + 2.5 + + + copy-profile + install + + copy-resources + + + target + + + ${distroDirectory} + true + + profile.xml + + + + + + + + + + + diff --git a/src/main/java/org/gcube/portets/user/message_conversations/client/MessageConversations.java b/src/main/java/org/gcube/portets/user/message_conversations/client/MessageConversations.java new file mode 100644 index 0000000..c7af180 --- /dev/null +++ b/src/main/java/org/gcube/portets/user/message_conversations/client/MessageConversations.java @@ -0,0 +1,39 @@ +package org.gcube.portets.user.message_conversations.client; + +import org.gcube.portets.user.message_conversations.client.ui.ApplicationView; + +import com.google.gwt.core.client.EntryPoint; +import com.google.gwt.core.client.GWT; +import com.google.gwt.user.client.rpc.ServiceDefTarget; +import com.google.gwt.user.client.ui.RootPanel; + +/** + * Entry point classes define onModuleLoad(). + */ +public class MessageConversations implements EntryPoint { + public static final String DIV_CONTAINER_ID = "create-users-container"; + /** + * Create a remote service proxy to talk to the server-side Greeting service. + */ + + private final MessageServiceAsync convService = GWT.create(MessageService.class); + private ApplicationView ap; + + /** + * This is the entry point method. + */ + public void onModuleLoad() { + ((ServiceDefTarget) convService).setServiceEntryPoint("/delegate/message-conversations"); + ap = new ApplicationView(); + RootPanel.get(DIV_CONTAINER_ID).add(ap); + ap.readUserMessages(false); + + } + + + + + + + +} diff --git a/src/main/java/org/gcube/portets/user/message_conversations/client/MessageService.java b/src/main/java/org/gcube/portets/user/message_conversations/client/MessageService.java new file mode 100644 index 0000000..6384697 --- /dev/null +++ b/src/main/java/org/gcube/portets/user/message_conversations/client/MessageService.java @@ -0,0 +1,25 @@ +package org.gcube.portets.user.message_conversations.client; + +import java.util.ArrayList; + +import org.gcube.portets.user.message_conversations.shared.ConvMessage; +import org.gcube.portets.user.message_conversations.shared.CurrUserAndPortalUsersWrapper; +import org.gcube.portets.user.message_conversations.shared.WSUser; + +import com.google.gwt.user.client.rpc.RemoteService; + +/** + * The client side stub for the RPC service. + */ +public interface MessageService extends RemoteService { + + ArrayList getMessages(boolean sent); + ConvMessage getMessageById(String messageId, boolean sent); + CurrUserAndPortalUsersWrapper getWorkspaceUsers(); + ArrayList searchUsers(String keyword); + boolean sendToById(ArrayList recipientIds, ArrayList listAttachmentsId, String subject, String body); + boolean deleteMessageById(String messageId, boolean sent); + String getAttachmentDownloadURL(String itemId); + boolean saveAttachmentToWorkspaceFolder(String itemId, String destinationFolderId); + boolean markMessageUnread(String messageId); +} diff --git a/src/main/java/org/gcube/portets/user/message_conversations/client/Utils.java b/src/main/java/org/gcube/portets/user/message_conversations/client/Utils.java new file mode 100644 index 0000000..040a4e1 --- /dev/null +++ b/src/main/java/org/gcube/portets/user/message_conversations/client/Utils.java @@ -0,0 +1,51 @@ +package org.gcube.portets.user.message_conversations.client; + +import java.util.Date; + +import com.google.gwt.i18n.client.DateTimeFormat; +import com.google.gwt.user.client.Random; +import com.google.gwt.user.client.ui.RootPanel; +import com.google.gwt.user.datepicker.client.CalendarUtil; + +import gwt.material.design.client.constants.Color; + +public class Utils { + public static DateTimeFormat fmCurrentYear = DateTimeFormat.getFormat("MMM d H:mm"); + public static DateTimeFormat fmPastYears = DateTimeFormat.getFormat("MMM d H:mm ''yy"); + + public static Color getRandomColor() { + Color toReturn = Color.values()[Random.nextInt(Color.values().length)]; + while (toReturn.name().startsWith("WHITE") || + toReturn.name().startsWith("GREY") || + toReturn.name().startsWith("TRANSPARENT") || + toReturn.name().startsWith("BROWN") || + toReturn.name().contains("LIGHTEN") || + toReturn.name().contains("ACCENT")) { + toReturn = Color.values()[Random.nextInt(Color.values().length)]; + } + return toReturn; + } + + + public static boolean isMobile() { + int screenWidth = RootPanel.get(MessageConversations.DIV_CONTAINER_ID).getOffsetWidth(); + return (screenWidth <= 768); + } + + @SuppressWarnings("deprecation") + public static String getFormatteDate(Date date) { + Date now = new Date(); + int dayInBetween = CalendarUtil.getDaysBetween(now, date); + switch (dayInBetween) { + case 0: + return"Today at " + (DateTimeFormat.getFormat("H:mm").format(date)); + case -1: + return "Yesterday, " + (DateTimeFormat.getFormat("H:mm").format(date)); + case -2: + return "2 days ago, " + (DateTimeFormat.getFormat("H:mm").format(date)); + default: + return(now.getYear() == date.getYear()) ? fmCurrentYear.format(date) : fmPastYears.format(date); + } + } + +} diff --git a/src/main/java/org/gcube/portets/user/message_conversations/client/autocomplete/MaterialAutoComplete.java b/src/main/java/org/gcube/portets/user/message_conversations/client/autocomplete/MaterialAutoComplete.java new file mode 100644 index 0000000..1df4399 --- /dev/null +++ b/src/main/java/org/gcube/portets/user/message_conversations/client/autocomplete/MaterialAutoComplete.java @@ -0,0 +1,706 @@ +package org.gcube.portets.user.message_conversations.client.autocomplete; + +import com.google.gwt.dom.client.Document; +import com.google.gwt.event.dom.client.*; +import com.google.gwt.event.logical.shared.*; +import com.google.gwt.event.shared.HandlerRegistration; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.ui.*; +import com.google.gwt.user.client.ui.SuggestOracle.Suggestion; +import gwt.material.design.addins.client.MaterialAddins; +import gwt.material.design.addins.client.autocomplete.constants.AutocompleteType; +import gwt.material.design.addins.client.base.constants.AddinsCssName; +import gwt.material.design.client.MaterialDesignBase; +import gwt.material.design.client.base.*; +import gwt.material.design.client.base.mixin.*; +import gwt.material.design.client.constants.CssName; +import gwt.material.design.client.constants.IconType; +import gwt.material.design.client.constants.ProgressType; +import gwt.material.design.client.ui.MaterialChip; +import gwt.material.design.client.ui.MaterialLabel; +import gwt.material.design.client.ui.html.Label; +import gwt.material.design.client.ui.html.ListItem; +import gwt.material.design.client.ui.html.UnorderedList; + +import java.util.*; +import java.util.Map.Entry; + +/** + * + * @author kevzlou7979 + * @author gilberto-torrezan + * @author M. Assante, CNR-ISTI + * @see Material AutoComplete + */ +// @formatter:on +public class MaterialAutoComplete extends AbstractValueWidget> implements HasPlaceholder, + HasProgress, HasType, HasSelectionHandlers, HasReadOnly { + + static { + if (MaterialAddins.isDebug()) { + MaterialDesignBase.injectCss(MaterialAutocompleteDebugClientBundle.INSTANCE.autocompleteCssDebug()); + } else { + MaterialDesignBase.injectCss(MaterialAutocompleteClientBundle.INSTANCE.autocompleteCss()); + } + } + + private Map suggestionMap = new LinkedHashMap<>(); + private Label placeholderLabel = new Label(); + + private List itemsHighlighted = new ArrayList<>(); + private FlowPanel panel = new FlowPanel(); + private UnorderedList list = new UnorderedList(); + private SuggestOracle suggestions; + private TextBox itemBox = new TextBox(); + private SuggestBox suggestBox; + private int limit = 0; + private MaterialLabel errorLabel = new MaterialLabel(); + private final ProgressMixin progressMixin = new ProgressMixin<>(this); + + private String selectedChipStyle = "blue white-text"; + private boolean directInputAllowed = true; + private MaterialChipProvider chipProvider = new DefaultMaterialChipProvider(); + + private final ErrorMixin errorMixin = new ErrorMixin<>(this, errorLabel, list, placeholderLabel); + + private FocusableMixin focusableMixin; + private ReadOnlyMixin readOnlyMixin; + + public final CssTypeMixin typeMixin = new CssTypeMixin<>(this, this); + + /** + * Use MaterialAutocomplete to search for matches from local or remote data + * sources. + */ + public MaterialAutoComplete() { + super(Document.get().createDivElement(), AddinsCssName.AUTOCOMPLETE, CssName.INPUT_FIELD); + add(panel); + } + + public MaterialAutoComplete(AutocompleteType type) { + this(); + setType(type); + } + + public MaterialAutoComplete(String placeholder) { + this(); + setPlaceholder(placeholder); + } + + /** + * Use MaterialAutocomplete to search for matches from local or remote data + * sources. + * + * @see #setSuggestions(SuggestOracle) + */ + public MaterialAutoComplete(SuggestOracle suggestions) { + this(); + build(suggestions); + } + + /** + * Generate and build the List Items to be set on Auto Complete box. + */ + protected void build(SuggestOracle suggestions) { + list.setStyleName(AddinsCssName.MULTIVALUESUGGESTBOX_LIST); + this.suggestions = suggestions; + final ListItem item = new ListItem(); + + item.setStyleName(AddinsCssName.MULTIVALUESUGGESTBOX_INPUT_TOKEN); + suggestBox = new SuggestBox(suggestions, itemBox); + setLimit(this.limit); + String autocompleteId = DOM.createUniqueId(); + itemBox.getElement().setId(autocompleteId); + + item.add(suggestBox); + item.add(placeholderLabel); + list.add(item); + + list.addDomHandler(event -> suggestBox.showSuggestionList(), ClickEvent.getType()); + + itemBox.addBlurHandler(blurEvent -> { + if (getValue().size() > 0) { + placeholderLabel.addStyleName(CssName.ACTIVE); + } + }); + + itemBox.addKeyDownHandler(event -> { + boolean changed = false; + + switch (event.getNativeKeyCode()) { + case KeyCodes.KEY_ENTER: + if (directInputAllowed) { + String value = itemBox.getValue(); + if (value != null && !(value = value.trim()).isEmpty()) { + gwt.material.design.client.base.Suggestion directInput = new gwt.material.design.client.base.Suggestion(); + directInput.setDisplay(value); + directInput.setSuggestion(value); + changed = addItem(directInput); + if (getType() == AutocompleteType.TEXT) { + itemBox.setText(value); + } else { + itemBox.setValue(""); + } + itemBox.setFocus(true); + } + } + break; + case KeyCodes.KEY_BACKSPACE: + if (itemBox.getValue().trim().isEmpty()) { + if (itemsHighlighted.isEmpty()) { + if (suggestionMap.size() > 0) { + ListItem li = (ListItem) list.getWidget(list.getWidgetCount() - 2); + + if (tryRemoveSuggestion(li.getWidget(0))) { + li.removeFromParent(); + changed = true; + } + } + } + } + case KeyCodes.KEY_DELETE: + if (itemBox.getValue().trim().isEmpty()) { + for (ListItem li : itemsHighlighted) { + if (tryRemoveSuggestion(li.getWidget(0))) { + li.removeFromParent(); + changed = true; + } + } + itemsHighlighted.clear(); + } + itemBox.setFocus(true); + break; + } + + if (changed) { + ValueChangeEvent.fire(MaterialAutoComplete.this, getValue()); + } + }); + + itemBox.addClickHandler(event -> suggestBox.showSuggestionList()); + + suggestBox.addSelectionHandler(selectionEvent -> { + Suggestion selectedItem = selectionEvent.getSelectedItem(); + itemBox.setValue(""); + if (addItem(selectedItem)) { + ValueChangeEvent.fire(MaterialAutoComplete.this, getValue()); + } + itemBox.setFocus(true); + }); + + panel.add(list); + panel.getElement().setAttribute("onclick", + "document.getElementById('" + autocompleteId + "').focus()"); + panel.add(errorLabel); + suggestBox.setFocus(true); + } + + protected boolean tryRemoveSuggestion(Widget widget) { + Set> entrySet = suggestionMap.entrySet(); + for (Entry entry : entrySet) { + if (widget.equals(entry.getValue())) { + if (chipProvider.isChipRemovable(entry.getKey())) { + suggestionMap.remove(entry.getKey()); + return true; + } + return false; + } + } + return false; + } + + /** + * Adding the item value using Material Chips added on auto complete box + */ + public boolean addItem(final Suggestion suggestion) { + SelectionEvent.fire(MaterialAutoComplete.this, suggestion); + if (getLimit() > 0) { + if (suggestionMap.size() >= getLimit()) { + return false; + } + } + + if (suggestionMap.containsKey(suggestion)) { + return false; + } + + final ListItem displayItem = new ListItem(); + displayItem.setStyleName(AddinsCssName.MULTIVALUESUGGESTBOX_TOKEN); + + if (getType() == AutocompleteType.TEXT) { + suggestionMap.clear(); + itemBox.setText(suggestion.getDisplayString()); + } else { + final MaterialChip chip = chipProvider.getChip(suggestion); + if (chip == null) { + return false; + } + + chip.addClickHandler(event -> { + if (chipProvider.isChipSelectable(suggestion)) { + if (itemsHighlighted.contains(displayItem)) { + chip.removeStyleName(selectedChipStyle); + itemsHighlighted.remove(displayItem); + } else { + chip.addStyleName(selectedChipStyle); + itemsHighlighted.add(displayItem); + } + } + }); + + if (chip.getIcon() != null) { + chip.getIcon().addClickHandler(event -> { + if (chipProvider.isChipRemovable(suggestion)) { + suggestionMap.remove(suggestion); + list.remove(displayItem); + itemsHighlighted.remove(displayItem); + ValueChangeEvent.fire(MaterialAutoComplete.this, getValue()); + suggestBox.showSuggestionList(); + } + }); + } + + suggestionMap.put(suggestion, chip); + displayItem.add(chip); + list.insert(displayItem, list.getWidgetCount() - 1); + } + return true; + } + + /** + * Clear the chip items on the autocomplete box + */ + public void clear() { + itemBox.setValue(""); + placeholderLabel.removeStyleName(CssName.ACTIVE); + + Collection values = suggestionMap.values(); + for (Widget widget : values) { + Widget parent = widget.getParent(); + if (parent instanceof ListItem) { + parent.removeFromParent(); + } + } + suggestionMap.clear(); + + clearErrorOrSuccess(); + } + + @Override + protected FocusableMixin getFocusableMixin() { + if (focusableMixin == null) { + focusableMixin = new FocusableMixin<>(new MaterialWidget(itemBox.getElement())); + } + return focusableMixin; + } + + /** + * @return the item values on autocomplete + * @see #getValue() + */ + public List getItemValues() { + Set keySet = suggestionMap.keySet(); + List values = new ArrayList<>(keySet.size()); + for (Suggestion suggestion : keySet) { + values.add(suggestion.getReplacementString()); + } + return values; + } + + /** + * @param itemValues the itemsSelected to set + * @see #setValue(Object) + */ + public void setItemValues(List itemValues) { + setItemValues(itemValues, false); + } + + /** + * @param itemValues the itemsSelected to set + * @param fireEvents will fire value change event if true + * @see #setValue(Object) + */ + public void setItemValues(List itemValues, boolean fireEvents) { + if (itemValues == null) { + clear(); + return; + } + List list = new ArrayList<>(itemValues.size()); + for (String value : itemValues) { + Suggestion suggestion = new gwt.material.design.client.base.Suggestion(value, value); + list.add(suggestion); + } + setValue(list, fireEvents); + if (itemValues.size() > 0) { + placeholderLabel.addStyleName(CssName.ACTIVE); + } + } + + /** + * @return the itemsHighlighted + */ + public List getItemsHighlighted() { + return itemsHighlighted; + } + + /** + * @param itemsHighlighted the itemsHighlighted to set + */ + public void setItemsHighlighted(List itemsHighlighted) { + this.itemsHighlighted = itemsHighlighted; + } + + /** + * @return the suggestion oracle + */ + public SuggestOracle getSuggestions() { + return suggestions; + } + + /** + * Sets the SuggestOracle to be used to provide suggestions. Also setups the + * component with the needed event handlers and UI elements. + * + * @param suggestions the suggestion oracle to set + */ + public void setSuggestions(SuggestOracle suggestions) { + this.suggestions = suggestions; + build(suggestions); + } + + public void setSuggestions(SuggestOracle suggestions, AutocompleteType type) { + setType(type); + setSuggestions(suggestions); + } + + public int getLimit() { + return limit; + } + + public void setLimit(int limit) { + this.limit = limit; + if (this.suggestBox != null) { + this.suggestBox.setLimit(limit); + } + } + + /** + * Set the number of suggestions to be displayed to the user. This differs from + * setLimit() which set both the suggestions displayed AND the limit of values + * allowed within the autocomplete. + * @param limit + */ + public void setAutoSuggestLimit(int limit) { + if (this.suggestBox != null) { + this.suggestBox.setLimit(limit); + } + } + + @Override + public String getPlaceholder() { + return placeholderLabel.getText(); + } + + @Override + public void setPlaceholder(String placeholder) { + placeholderLabel.setText(placeholder); + } + + /** + * Gets the current {@link MaterialChipProvider}. By default, the class uses + * an instance of {@link DefaultMaterialChipProvider}. + */ + public MaterialChipProvider getChipProvider() { + return chipProvider; + } + + /** + * Sets a {@link MaterialChipProvider} that can customize how the + * {@link MaterialChip} is created for each selected {@link Suggestion}. + */ + public void setChipProvider(MaterialChipProvider chipProvider) { + this.chipProvider = chipProvider; + } + + /** + * When set to false, only {@link Suggestion}s from the + * SuggestionOracle are accepted. Direct input create by the user is + * ignored. By default, direct input is allowed. + */ + public void setDirectInputAllowed(boolean directInputAllowed) { + this.directInputAllowed = directInputAllowed; + } + + /** + * @return if {@link Suggestion}s created by direct input from the user + * should be allowed. By default directInputAllowed is + * true. + */ + public boolean isDirectInputAllowed() { + return directInputAllowed; + } + + /** + * Sets the style class applied to chips when they are selected. + *

+ * Defaults to "blue white-text". + *

+ * + * @param selectedChipStyle The class or classes to be applied to selected chips + */ + public void setSelectedChipStyle(String selectedChipStyle) { + this.selectedChipStyle = selectedChipStyle; + } + + /** + * Returns the style class applied to chips when they are selected. + *

+ * Defaults to "blue white-text". + *

+ */ + public String getSelectedChipStyle() { + return selectedChipStyle; + } + + @Override + public void showProgress(ProgressType type) { + progressMixin.showProgress(ProgressType.INDETERMINATE); + } + + @Override + public void setPercent(double percent) { + progressMixin.setPercent(percent); + } + + @Override + public void hideProgress() { + progressMixin.hideProgress(); + } + + @Override + public HandlerRegistration addKeyUpHandler(final KeyUpHandler handler) { + return itemBox.addKeyUpHandler(event -> { + if (isEnabled()) { + handler.onKeyUp(event); + } + }); + } + + @Override + public void setType(AutocompleteType type) { + typeMixin.setType(type); + } + + @Override + public AutocompleteType getType() { + return typeMixin.getType(); + } + + @Override + public HandlerRegistration addSelectionHandler(final SelectionHandler handler) { + return addHandler(new SelectionHandler() { + @Override + public void onSelection(SelectionEvent event) { + if (isEnabled()) { + handler.onSelection(event); + } + } + }, SelectionEvent.getType()); + } + + public ReadOnlyMixin getReadOnlyMixin() { + if (readOnlyMixin == null) { + readOnlyMixin = new ReadOnlyMixin<>(this, itemBox); + } + return readOnlyMixin; + } + + @Override + public void setReadOnly(boolean value) { + getReadOnlyMixin().setReadOnly(value); + if (value) { + setEnabled(false); + } + } + + @Override + public boolean isReadOnly() { + return getReadOnlyMixin().isReadOnly(); + } + + @Override + public void setToggleReadOnly(boolean toggle) { + getReadOnlyMixin().setToggleReadOnly(toggle); + } + + @Override + public boolean isToggleReadOnly() { + return getReadOnlyMixin().isToggleReadOnly(); + } + + /** + * Interface that defines how a {@link MaterialChip} is created, given a + * {@link Suggestion}. + * + * @see MaterialAutoComplete#setChipProvider(MaterialChipProvider) + */ + public static interface MaterialChipProvider { + + /** + * Creates and returns a {@link MaterialChip} based on the selected + * {@link Suggestion}. + * + * @param suggestion the selected {@link Suggestion} + * @return the created MaterialChip, or null if the + * suggestion should be ignored. + */ + MaterialChip getChip(Suggestion suggestion); + + /** + * Returns whether the chip defined by the suggestion should be selected when the user clicks on it. + *

+ *

+ * Selecion of chips is used to batch remove suggestions, for example. + *

+ * + * @param suggestion the selected {@link Suggestion} + * @see MaterialAutoComplete#setSelectedChipStyle(String) + */ + boolean isChipSelectable(Suggestion suggestion); + + /** + * Returns whether the chip defined by the suggestion should be removed from the autocomplete when clicked on its icon. + *

+ *

+ * Override this method returning false to implement your own logic when the user clicks on the chip icon. + *

+ * + * @param suggestion the selected {@link Suggestion} + */ + boolean isChipRemovable(Suggestion suggestion); + } + + /** + * Default implementation of the {@link MaterialChipProvider} interface, + * used by the {@link MaterialAutoComplete}. + *

+ *

+ * By default all chips are selectable and removable. The default {@link IconType} used by the chips provided is the {@link IconType#CLOSE}. + *

+ * + * @see MaterialAutoComplete#setChipProvider(MaterialChipProvider) + */ + public static class DefaultMaterialChipProvider implements MaterialChipProvider { + + @Override + public MaterialChip getChip(Suggestion suggestion) { + final MaterialChip chip = new MaterialChip(); + + String imageChip = suggestion.getDisplayString(); + String textChip = imageChip; + + String s = "]*[>]", ""); + } + chip.setText(textChip); + chip.setIconType(IconType.CLOSE); + + return chip; + } + + @Override + public boolean isChipRemovable(Suggestion suggestion) { + return true; + } + + @Override + public boolean isChipSelectable(Suggestion suggestion) { + return true; + } + } + + @Override + public HandlerRegistration addValueChangeHandler(final ValueChangeHandler> handler) { + return addHandler(new ValueChangeHandler>() { + @Override + public void onValueChange(ValueChangeEvent> event) { + if (isEnabled()) { + handler.onValueChange(event); + } + } + }, ValueChangeEvent.getType()); + } + + @Override + public HandlerRegistration addBlurHandler(BlurHandler handler) { + return itemBox.addHandler(blurEvent -> { + if (isEnabled()) { + handler.onBlur(blurEvent); + } + }, BlurEvent.getType()); + } + + @Override + public HandlerRegistration addFocusHandler(FocusHandler handler) { + return itemBox.addHandler(focusEvent -> { + if (isEnabled()) { + handler.onFocus(focusEvent); + } + }, FocusEvent.getType()); + } + + /** + * Returns the selected {@link Suggestion}s. Modifications to the list are + * not propagated to the component. + * + * @return the list of selected {@link Suggestion}s, or empty if none was + * selected (never null). + */ + @Override + public List getValue() { + return new ArrayList<>(suggestionMap.keySet()); + } + + @Override + public void setValue(List value, boolean fireEvents) { + clear(); + if (value != null) { + placeholderLabel.addStyleName(CssName.ACTIVE); + for (Suggestion suggestion : value) { + addItem(suggestion); + } + } + super.setValue(value, fireEvents); + } + + @Override + public void setEnabled(boolean enabled) { + super.setEnabled(enabled); + itemBox.setEnabled(enabled); + } + + @Override + public ErrorMixin getErrorMixin() { + return errorMixin; + } + + public Label getPlaceholderLabel() { + return placeholderLabel; + } + + public TextBox getItemBox() { + return itemBox; + } + + public MaterialLabel getErrorLabel() { + return errorLabel; + } + + public SuggestBox getSuggestBox() { + return suggestBox; + } +} diff --git a/src/main/java/org/gcube/portets/user/message_conversations/client/autocomplete/MaterialAutocompleteClientBundle.java b/src/main/java/org/gcube/portets/user/message_conversations/client/autocomplete/MaterialAutocompleteClientBundle.java new file mode 100644 index 0000000..f57f7c2 --- /dev/null +++ b/src/main/java/org/gcube/portets/user/message_conversations/client/autocomplete/MaterialAutocompleteClientBundle.java @@ -0,0 +1,20 @@ +package org.gcube.portets.user.message_conversations.client.autocomplete; + + + +import com.google.gwt.core.client.GWT; +import com.google.gwt.resources.client.ClientBundle; +import com.google.gwt.resources.client.TextResource; + +/** + * Client Bundle for Autocomplete component + * + * @author kevzlou7979 + */ +interface MaterialAutocompleteClientBundle extends ClientBundle { + + MaterialAutocompleteClientBundle INSTANCE = GWT.create(MaterialAutocompleteClientBundle.class); + + @Source("autocomplete.min.css") + TextResource autocompleteCss(); +} diff --git a/src/main/java/org/gcube/portets/user/message_conversations/client/autocomplete/MaterialAutocompleteDebugClientBundle.java b/src/main/java/org/gcube/portets/user/message_conversations/client/autocomplete/MaterialAutocompleteDebugClientBundle.java new file mode 100644 index 0000000..1ecaf83 --- /dev/null +++ b/src/main/java/org/gcube/portets/user/message_conversations/client/autocomplete/MaterialAutocompleteDebugClientBundle.java @@ -0,0 +1,20 @@ +package org.gcube.portets.user.message_conversations.client.autocomplete; + + + +import com.google.gwt.core.client.GWT; +import com.google.gwt.resources.client.ClientBundle; +import com.google.gwt.resources.client.TextResource; + +/** + * Client Bundle for Autocomplete component + * + * @author kevzlou7979 + */ +interface MaterialAutocompleteDebugClientBundle extends ClientBundle { + + MaterialAutocompleteDebugClientBundle INSTANCE = GWT.create(MaterialAutocompleteDebugClientBundle.class); + + @Source("autocomplete.css") + TextResource autocompleteCssDebug(); +} diff --git a/src/main/java/org/gcube/portets/user/message_conversations/client/autocomplete/autocomplete.css b/src/main/java/org/gcube/portets/user/message_conversations/client/autocomplete/autocomplete.css new file mode 100644 index 0000000..5598827 --- /dev/null +++ b/src/main/java/org/gcube/portets/user/message_conversations/client/autocomplete/autocomplete.css @@ -0,0 +1,103 @@ +ul.multiValueSuggestBox-list { + overflow: hidden; + height: auto !important; + height: 1%; + cursor: text; + min-height: 1px; + z-index: 999; + margin: 0; + padding: 0; + border: none; + width: 100%; + list-style-type: none; + margin-bottom: 15px; + border-bottom: 1px solid #9e9e9e; +} + +.autocomplete.disabled ul.multiValueSuggestBox-list { + border-bottom: 1px dotted rgba(0, 0, 0, 0.26); +} + +.autocomplete.disabled .chip i { + display: none; +} + +.autocomplete-text .multiValueSuggestBox-input-token, .autocomplete-text .multiValueSuggestBox-input-token input { + width: 100% !important; +} + +.autocomplete.read-only ul.multiValueSuggestBox-list { + border: none !important; +} + +.autocomplete.disabled .chip { + color: rgba(0,0,0,0.6) !important; + background-color: #e4e4e4 !important; +} + +.gwt-SuggestBoxPopup .item { + padding-left: 20px !important; + padding: 12px; +} + +.gwt-SuggestBoxPopup .item img { + border-radius: 12px; + float: left; + height: 24px; + width: 24px; + margin-right: 12px; +} +.gwt-SuggestBoxPopup { + z-index: 9999; + min-width: 300px !important; + background: white; + border-radius: 2px; + -webkit-box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.16), 0 2px 10px 0 rgba(0, 0, 0, 0.12); + -moz-box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.16), 0 2px 10px 0 rgba(0, 0, 0, 0.12); + box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.16), 0 2px 10px 0 rgba(0, 0, 0, 0.12); +} +.gwt-SuggestBoxPopup tr { + white-space: nowrap; +} +.gwt-SuggestBoxPopup .item-selected { + background: rgba(0,0,0,.03) !important; +} +.suggestPopupMiddleCenter { + width: 100%; + padding: 0 !important; +} +.suggestPopupMiddle { + width: 100%; + left: 0 !important; +} + +.suggestPopupTop, .suggestPopupBottom, .suggestPopupMiddleLeft, .suggestPopupMiddleRight { + display: none; +} + +ul.multiValueSuggestBox-list li input { + border: 0; + width: 200px; + background-color: transparent; + margin: 0px; + border: none !important; +} + +ul.multiValueSuggestBox-list input[type=text]:focus:not([readonly]) { + border: none !important; + box-shadow: 0 1px 0 0 #fff !important; +} + +li.multiValueSuggestBox-token { + cursor: default; + float: left; + margin: 8px 0 0 8px; +} + +li.multiValueSuggestBox-input-token { + float: left; +} + +.row .autocomplete label { + left: 0.75rem !important; +} \ No newline at end of file diff --git a/src/main/java/org/gcube/portets/user/message_conversations/client/autocomplete/autocomplete.min.css b/src/main/java/org/gcube/portets/user/message_conversations/client/autocomplete/autocomplete.min.css new file mode 100644 index 0000000..1546880 --- /dev/null +++ b/src/main/java/org/gcube/portets/user/message_conversations/client/autocomplete/autocomplete.min.css @@ -0,0 +1 @@ +.autocomplete.disabled .chip i,.suggestPopupBottom,.suggestPopupMiddleLeft,.suggestPopupMiddleRight,.suggestPopupTop{display:none}ul.multiValueSuggestBox-list{overflow:hidden;height:auto!important;height:1%;cursor:text;min-height:1px;z-index:999;margin:0 0 15px;padding:0;border:none;width:100%;list-style-type:none;border-bottom:1px solid #9e9e9e}.autocomplete.disabled ul.multiValueSuggestBox-list{border-bottom:1px dotted rgba(0,0,0,.26)}.autocomplete-text .multiValueSuggestBox-input-token,.autocomplete-text .multiValueSuggestBox-input-token input{width:100%!important}.autocomplete.read-only ul.multiValueSuggestBox-list{border:none!important}.autocomplete.disabled .chip{color:rgba(0,0,0,.6)!important;background-color:#e4e4e4!important}.gwt-SuggestBoxPopup .item{padding-left:20px!important;padding:12px}.gwt-SuggestBoxPopup .item img{border-radius:12px;float:left;height:24px;width:24px;margin-right:12px}.gwt-SuggestBoxPopup{z-index:9999;min-width:300px!important;background:#fff;border-radius:2px;-webkit-box-shadow:0 2px 5px 0 rgba(0,0,0,.16),0 2px 10px 0 rgba(0,0,0,.12);-moz-box-shadow:0 2px 5px 0 rgba(0,0,0,.16),0 2px 10px 0 rgba(0,0,0,.12);box-shadow:0 2px 5px 0 rgba(0,0,0,.16),0 2px 10px 0 rgba(0,0,0,.12)}.gwt-SuggestBoxPopup tr{white-space:nowrap}.gwt-SuggestBoxPopup .item-selected{background:rgba(0,0,0,.03)!important}.suggestPopupMiddleCenter{width:100%;padding:0!important}.suggestPopupMiddle{width:100%;left:0!important}ul.multiValueSuggestBox-list li input{width:200px;background-color:transparent;margin:0;border:none!important}ul.multiValueSuggestBox-list input[type=text]:focus:not([readonly]){border:none!important;box-shadow:0 1px 0 0 #fff!important}li.multiValueSuggestBox-token{cursor:default;float:left;margin:8px 0 0 8px}li.multiValueSuggestBox-input-token{float:left}.row .autocomplete label{left:.75rem!important} \ No newline at end of file diff --git a/src/main/java/org/gcube/portets/user/message_conversations/client/event/DeleteMessageEvent.java b/src/main/java/org/gcube/portets/user/message_conversations/client/event/DeleteMessageEvent.java new file mode 100644 index 0000000..5109dab --- /dev/null +++ b/src/main/java/org/gcube/portets/user/message_conversations/client/event/DeleteMessageEvent.java @@ -0,0 +1,35 @@ +package org.gcube.portets.user.message_conversations.client.event; + +import org.gcube.portets.user.message_conversations.shared.ConvMessage; + +import com.google.gwt.event.shared.GwtEvent; + + + +public class DeleteMessageEvent extends GwtEvent { + public static Type TYPE = new Type(); + + private ConvMessage m; + private boolean sent; + public DeleteMessageEvent(ConvMessage m, boolean sent) { + this.m = m; + } + + public ConvMessage getMessage() { + return m; + } + + public boolean isSent() { + return sent; + } + + @Override + public Type getAssociatedType() { + return TYPE; + } + + @Override + protected void dispatch(DeleteMessageEventHandler handler) { + handler.onDeleteMessage(this); + } +} diff --git a/src/main/java/org/gcube/portets/user/message_conversations/client/event/DeleteMessageEventHandler.java b/src/main/java/org/gcube/portets/user/message_conversations/client/event/DeleteMessageEventHandler.java new file mode 100644 index 0000000..ba20fec --- /dev/null +++ b/src/main/java/org/gcube/portets/user/message_conversations/client/event/DeleteMessageEventHandler.java @@ -0,0 +1,7 @@ +package org.gcube.portets.user.message_conversations.client.event; + +import com.google.gwt.event.shared.EventHandler; + +public interface DeleteMessageEventHandler extends EventHandler { + void onDeleteMessage(DeleteMessageEvent event); +} diff --git a/src/main/java/org/gcube/portets/user/message_conversations/client/event/NewMessageEvent.java b/src/main/java/org/gcube/portets/user/message_conversations/client/event/NewMessageEvent.java new file mode 100644 index 0000000..fd4bacc --- /dev/null +++ b/src/main/java/org/gcube/portets/user/message_conversations/client/event/NewMessageEvent.java @@ -0,0 +1,40 @@ +package org.gcube.portets.user.message_conversations.client.event; + +import java.util.HashSet; + +import com.google.gwt.event.shared.GwtEvent; + + + +public class NewMessageEvent extends GwtEvent { + public static Type TYPE = new Type(); + + + private String text; + private HashSet mentionedUsers; + + public NewMessageEvent() { + + this.text = text; + this.mentionedUsers = mentionedUsers; + } + + + public String getText() { + return text; + } + + public HashSet getMentionedUsers() { + return mentionedUsers; + } + + @Override + public Type getAssociatedType() { + return TYPE; + } + + @Override + protected void dispatch(NewMessageEventHandler handler) { + handler.onNewMessage(this); + } +} diff --git a/src/main/java/org/gcube/portets/user/message_conversations/client/event/NewMessageEventHandler.java b/src/main/java/org/gcube/portets/user/message_conversations/client/event/NewMessageEventHandler.java new file mode 100644 index 0000000..30473a2 --- /dev/null +++ b/src/main/java/org/gcube/portets/user/message_conversations/client/event/NewMessageEventHandler.java @@ -0,0 +1,7 @@ +package org.gcube.portets.user.message_conversations.client.event; + +import com.google.gwt.event.shared.EventHandler; + +public interface NewMessageEventHandler extends EventHandler { + void onNewMessage(NewMessageEvent event); +} diff --git a/src/main/java/org/gcube/portets/user/message_conversations/client/oracle/UserOracle.java b/src/main/java/org/gcube/portets/user/message_conversations/client/oracle/UserOracle.java new file mode 100644 index 0000000..9e6293b --- /dev/null +++ b/src/main/java/org/gcube/portets/user/message_conversations/client/oracle/UserOracle.java @@ -0,0 +1,60 @@ +package org.gcube.portets.user.message_conversations.client.oracle; +import gwt.material.design.addins.client.autocomplete.base.MaterialSuggestionOracle; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; + +import org.gcube.portets.user.message_conversations.client.MessageService; +import org.gcube.portets.user.message_conversations.client.MessageServiceAsync; +import org.gcube.portets.user.message_conversations.shared.CurrUserAndPortalUsersWrapper; +import org.gcube.portets.user.message_conversations.shared.WSUser; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.gwt.user.client.rpc.ServiceDefTarget; + +public class UserOracle extends MaterialSuggestionOracle{ + private final MessageServiceAsync convService = GWT.create(MessageService.class); + + private List contacts = new LinkedList<>(); + + public void addContacts(List users) { + contacts.addAll(users); + } + + public UserOracle() { + ((ServiceDefTarget) convService).setServiceEntryPoint("/delegate/message-conversations"); + } + + @Override + public void requestSuggestions(Request request, Callback callback) { + Response resp = new Response(); + if(contacts.isEmpty()){ + callback.onSuggestionsReady(request, resp); + return; + } + String text = request.getQuery(); + text = text.toLowerCase(); + + final List list = new ArrayList<>(); + convService.searchUsers(text, new AsyncCallback>() { + + @Override + public void onFailure(Throwable arg0) { + GWT.log("error"); + callback.onSuggestionsReady(request, resp); + return; + } + + @Override + public void onSuccess(ArrayList results) { + for (WSUser contact : results){ + list.add(new UserSuggestion(contact)); + } + resp.setSuggestions(list); + callback.onSuggestionsReady(request, resp); + } + }); + } +} \ No newline at end of file diff --git a/src/main/java/org/gcube/portets/user/message_conversations/client/oracle/UserSuggestion.java b/src/main/java/org/gcube/portets/user/message_conversations/client/oracle/UserSuggestion.java new file mode 100644 index 0000000..7b8dba7 --- /dev/null +++ b/src/main/java/org/gcube/portets/user/message_conversations/client/oracle/UserSuggestion.java @@ -0,0 +1,28 @@ +package org.gcube.portets.user.message_conversations.client.oracle; + +import org.gcube.portets.user.message_conversations.shared.WSUser; + +import com.google.gwt.user.client.ui.SuggestOracle; + +public class UserSuggestion implements SuggestOracle.Suggestion { + + private WSUser user; + + public UserSuggestion(WSUser user) { + this.user = user; + } + + @Override + public String getDisplayString() { + return getReplacementString(); + } + + @Override + public String getReplacementString() { + return user.getFullName() + " ("+user.getEmail()+")"; + } + + public WSUser getUser() { + return user; + } +} diff --git a/src/main/java/org/gcube/portets/user/message_conversations/client/ui/ApplicationView.java b/src/main/java/org/gcube/portets/user/message_conversations/client/ui/ApplicationView.java new file mode 100755 index 0000000..5bbff79 --- /dev/null +++ b/src/main/java/org/gcube/portets/user/message_conversations/client/ui/ApplicationView.java @@ -0,0 +1,380 @@ +package org.gcube.portets.user.message_conversations.client.ui; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.List; + +import org.gcube.portets.user.message_conversations.client.MessageService; +import org.gcube.portets.user.message_conversations.client.MessageServiceAsync; +import org.gcube.portets.user.message_conversations.client.Utils; +import org.gcube.portets.user.message_conversations.client.event.DeleteMessageEvent; +import org.gcube.portets.user.message_conversations.client.ui.resources.MessagesResources; +import org.gcube.portets.user.message_conversations.shared.ConvMessage; +import org.gcube.portets.user.message_conversations.shared.MessageUserModel; + +import com.google.gwt.core.client.GWT; + +/* + * #%L + * GwtMaterial + * %% + * Copyright (C) 2015 - 2016 GwtMaterialDesign + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + + +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.logical.shared.ResizeEvent; +import com.google.gwt.event.logical.shared.ResizeHandler; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.uibinder.client.UiField; +import com.google.gwt.uibinder.client.UiHandler; +import com.google.gwt.user.client.Window; +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.gwt.user.client.rpc.ServiceDefTarget; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.HTML; +import com.google.gwt.user.client.ui.HTMLPanel; +import com.google.gwt.user.client.ui.RootPanel; +import com.google.gwt.user.client.ui.ScrollPanel; +import com.google.gwt.user.client.ui.Widget; + +import gwt.material.design.client.constants.IconType; +import gwt.material.design.client.constants.Position; +import gwt.material.design.client.ui.MaterialAnchorButton; +import gwt.material.design.client.ui.MaterialBadge; +import gwt.material.design.client.ui.MaterialCollection; +import gwt.material.design.client.ui.MaterialFAB; +import gwt.material.design.client.ui.MaterialLink; +import gwt.material.design.client.ui.MaterialProgress; +import gwt.material.design.client.ui.MaterialRow; +import gwt.material.design.client.ui.MaterialToast; +import gwt.material.design.client.ui.animate.MaterialAnimation; +import gwt.material.design.client.ui.animate.Transition; + +public class ApplicationView extends Composite { + + interface Binder extends UiBinder { + } + + private static Binder uiBinder = GWT.create(Binder.class); + private final MessageServiceAsync convService = GWT.create(MessageService.class); + + + private boolean toggle = false; + private boolean toggleSwitch = false; + private int unreadMessages = 0; + private int totalMessages = 0; + + + @UiField ScrollPanel scrollerPanel; + @UiField MaterialRow rightPanel; + @UiField HTMLPanel htmlPanel; + @UiField MaterialFAB FAB; + @UiField MaterialLink menu, newMessage, switcher; + @UiField MaterialAnchorButton replyAll, reply, forward; + @UiField MaterialCollection messagesCollection; + @UiField MaterialProgress messageLoader, messagesLoader; + @UiField MaterialBadge badge; + + private ConvMessage currentSelected; + private DisplayMessage displayMessage; + private WriteMessage newMessageDisplay; + + + public ApplicationView() { + initWidget(uiBinder.createAndBindUi(this)); + ((ServiceDefTarget) convService).setServiceEntryPoint("/delegate/message-conversations"); + displayMessage = new DisplayMessage(convService, this); + newMessageDisplay = new WriteMessage(convService, this); + Window.addResizeHandler(new ResizeHandler() { + @Override + public void onResize(ResizeEvent event) { + int height = event.getHeight() - 100; + scrollerPanel.setHeight(height+"px"); + } + }); + scrollerPanel.add(displayMessage); + if (! Utils.isMobile()) { + reply.setTooltip("Reply"); + reply.setTooltipPosition(Position.LEFT); + replyAll.setTooltip("Reply All"); + replyAll.setTooltipPosition(Position.LEFT); + forward.setTooltip("Forward"); + forward.setTooltipPosition(Position.LEFT); + } + } + + /** + * + * @param sent + */ + public void readUserMessages(final boolean sent) { + messagesLoader.setVisible(true); + convService.getMessages(sent, new AsyncCallback>() { + @Override + public void onFailure(Throwable arg0) { + RootPanel.get("create-users-container").add(new HTML("ERROR ")); + } + + @Override + public void onSuccess(ArrayList messages) { + if (messages != null) { + showMessages(messages, sent); + if (messages.size() > 0) + readUserMessage(messages.get(0).getId(), sent); + else { + writeWelcomeMessage(); + messageLoader.setVisible(false); + } + } else { + RootPanel.get("create-users-container").add(new HTML("It is impossible to read your messages at this time. " + + "This could be a server error, please try to reload the page")); + } + } + }); + } + /** + * + * @param messages + * @param sent + */ + public void showMessages(ArrayList messages, boolean sent) { + messagesCollection.clear(); + messagesLoader.setVisible(false); + messagesCollection.setVisible(true); + int scrollerHeight = Window.getClientHeight() - 100; + scrollerPanel.setHeight(scrollerHeight+"px"); + for (ConvMessage convMessage : messages) { + messagesCollection.add(new MessageItem(convMessage, messagesCollection, this, sent)); + if (! (sent || convMessage.isRead())) + unreadMessages++; + totalMessages++; + } + if (messages.size() > 0) { + MessageItem first = (MessageItem) messagesCollection.getChildrenList().get(0); + first.setSelected(true); + } else { + + } + updateBadge(sent); + } + /** + * + * @param event + */ + public void deleteMessage(final DeleteMessageEvent event) { + final ConvMessage toDelete = event.getMessage(); + boolean sent = event.isSent(); + convService.deleteMessageById(toDelete.getId(), sent, new AsyncCallback() { + @Override + public void onFailure(Throwable caught) { + MaterialToast.fireToast("Message Deleted Failed for ("+toDelete.getSubject()+")"); + } + @Override + public void onSuccess(Boolean result) { + MaterialToast.fireToast("Message Deleted ("+toDelete.getSubject()+")"); + totalMessages = totalMessages - 1; + updateBadge(event.isSent()); + } + }); + } + + /** + * + * @param event + */ + public void setMessageUnread(ConvMessage toSet) { + convService.markMessageUnread(toSet.getId(), new AsyncCallback() { + @Override + public void onFailure(Throwable caught) { + } + @Override + public void onSuccess(Boolean result) { + unreadMessages++; + updateBadge(false); + } + }); + } + + + /** + * + * @param messageId + * @param sent + */ + public void readUserMessage(String messageId, final boolean sent) { + messageLoader.setColor(Utils.getRandomColor()); + messageLoader.setVisible(true); + scrollerPanel.clear(); + convService.getMessageById(messageId, toggleSwitch, new AsyncCallback() { + @Override + public void onFailure(Throwable arg0) { + RootPanel.get("create-users-container").add(new HTML("ERROR getting message ")); + messageLoader.setVisible(false); + } + + @Override + public void onSuccess(ConvMessage toShow) { + messageLoader.setVisible(false); + displayMessage.showMessage(toShow); + scrollerPanel.add(displayMessage); + setCurrentSelectedMessage(toShow); + if (!sent) + decreaseUnreadCounter(toShow); + FAB.setVisible(true); + } + }); + } + private void updateBadge(boolean sent) { + String badgeText = totalMessages + (sent ? " sent" : " (" + unreadMessages + " unread" + ")"); + if (unreadMessages == 0 && !sent) + badgeText = totalMessages+" received"; + badge.setText(badgeText); + badge.setVisible(true); + } + private void decreaseUnreadCounter(ConvMessage toShow) { + if (unreadMessages > 0 && !toShow.isRead()) + unreadMessages--; + updateBadge(false); + } + + private void resetCounters() { + badge.setVisible(false); + unreadMessages = 0; + totalMessages = 0; + } + + + + @UiHandler("switcher") + void onSwitchSentInbox(ClickEvent e) { + resetCounters(); + MaterialAnimation animation = new MaterialAnimation(); + animation.setDelay(0); + animation.setDuration(1000); + animation.transition(Transition.FLIPINX); + animation.animate(switcher); + if(!toggleSwitch){ + switcher.setIconType(IconType.SEND); + }else{ + switcher.setIconType(IconType.INBOX); + } + toggleSwitch = ! toggleSwitch; + readUserMessages(toggleSwitch); + } + + @UiHandler("menu") + void onHideShowSidebar(ClickEvent e) { + if(toggle){ + showSidePanel(); + }else{ + hideSidePanel(); + } + } + + + private void displayNewOrReplyMessage() { + FAB.setVisible(false); + messagesCollection.clearActive(); + scrollerPanel.clear(); + scrollerPanel.add(newMessageDisplay); + newMessageDisplay.setFocusOnUsersInput(); + if (Utils.isMobile()) + hideSidePanel(); + } + + @UiHandler("newMessage") + void onNewMessage(ClickEvent e) { + newMessageDisplay = new WriteMessage(convService, this); + displayNewOrReplyMessage(); + } + + @UiHandler("reply") + void onReplyMessage(ClickEvent e) { + if (currentSelected != null) { + newMessageDisplay = new WriteMessage(convService, this); + ConvMessage msg = currentSelected; + newMessageDisplay.setIsReply(msg); + displayNewOrReplyMessage(); + } + else { + Window.alert("Cannot find which message to reply, please report the issue"); + } + } + @UiHandler("replyAll") + void onReplyAllMessage(ClickEvent e) { + if (currentSelected != null) { + newMessageDisplay = new WriteMessage(convService, this); + ConvMessage msg = currentSelected; + newMessageDisplay.setIsReplyAll(msg); + displayNewOrReplyMessage(); + } + else { + Window.alert("Cannot find which message to reply all, please report the issue"); + } + } + @UiHandler("forward") + void onForwardMessage(ClickEvent e) { + if (currentSelected != null) { + newMessageDisplay = new WriteMessage(convService, this); + ConvMessage msg = currentSelected; + newMessageDisplay.setIsForward(msg); + displayNewOrReplyMessage(); + } + else { + Window.alert("Cannot find which message to forward, please report the issue"); + } + } + protected void showSidePanel() { + displayMessage.getMainPanel().setLeft(350); + displayMessage.getMainPanel().setGrid("l8 m12 s12"); + newMessageDisplay.getMainPanel().setLeft(350); + newMessageDisplay.getMainPanel().setGrid("l8 m12 s12"); + rightPanel.setLeft(0); + toggle = ! toggle; + } + + protected void hideSidePanel() { + displayMessage.getMainPanel().setLeft(0); + displayMessage.getMainPanel().setGrid("l12 s12 m12"); + newMessageDisplay.getMainPanel().setLeft(0); + newMessageDisplay.getMainPanel().setGrid("l12 s12 m12"); + + rightPanel.setLeft(-350); + toggle = ! toggle; + } + + private void setCurrentSelectedMessage(ConvMessage msg) { + currentSelected = msg; + } + + private void writeWelcomeMessage() { + MessagesResources images = GWT.create(MessagesResources.class); + List recipients = Arrays.asList(new MessageUserModel(0, "you", "You", "")); + @SuppressWarnings("deprecation") + MessageUserModel sender = new MessageUserModel(0, "jarvis", "D4Science Team", images.d4scienceTeam().getURL(), "", ""); + ConvMessage welcomeMessage = new ConvMessage("0", "Welcome to your Messages", sender, recipients , new Date(), MessagesResources.WELCOME_MESSAGE, true); + FAB.setVisible(false); + displayMessage.showMessage(welcomeMessage); + welcomeMessage.setContent(MessagesResources.WELCOME_MESSAGE.substring(0, 102)+ " ..."); + MessageItem item = new MessageItem(welcomeMessage, messagesCollection, this, false); + item.hideMessageMenu(); + messagesCollection.add(item); + totalMessages++; + updateBadge(false); + } +} diff --git a/src/main/java/org/gcube/portets/user/message_conversations/client/ui/ApplicationView.ui.xml b/src/main/java/org/gcube/portets/user/message_conversations/client/ui/ApplicationView.ui.xml new file mode 100755 index 0000000..40fa36e --- /dev/null +++ b/src/main/java/org/gcube/portets/user/message_conversations/client/ui/ApplicationView.ui.xml @@ -0,0 +1,100 @@ + + + + .animation { + transition: 0.4s all; + -webkit-transition: 0.4s all; + -moz-transition: 0.4s all; + } + + .borderRight { + border-right: 1px solid #ccc; + } + + .topBar { + height: 60px; + overflow-y: hidden !important; /* needed in windows */ + border-bottom: 1px solid #999; + } + + .marginTop { + margin-top: 0px; + } + + .overflowXHidden { + overflow-x: hidden !important; /* needed in windows */ + } + + .modalTitle { + font-size: 2em; + } + + .alignCenter { + text-align: center; + padding: 50px; + } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/java/org/gcube/portets/user/message_conversations/client/ui/AttachmentMenu.java b/src/main/java/org/gcube/portets/user/message_conversations/client/ui/AttachmentMenu.java new file mode 100644 index 0000000..e5e39eb --- /dev/null +++ b/src/main/java/org/gcube/portets/user/message_conversations/client/ui/AttachmentMenu.java @@ -0,0 +1,134 @@ +package org.gcube.portets.user.message_conversations.client.ui; + +import org.gcube.portets.user.message_conversations.client.MessageServiceAsync; +import org.gcube.portets.user.message_conversations.shared.FileModel; +import org.gcube.portlets.widgets.wsexplorer.client.notification.WorkspaceExplorerSaveNotification.WorskpaceExplorerSaveNotificationListener; +import org.gcube.portlets.widgets.wsexplorer.client.save.WorkspaceExplorerSaveDialog; +import org.gcube.portlets.widgets.wsexplorer.shared.Item; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.dom.client.Style.Unit; +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.uibinder.client.UiField; +import com.google.gwt.uibinder.client.UiHandler; +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.Widget; + +import gwt.material.design.client.ui.MaterialButton; +import gwt.material.design.client.ui.MaterialDropDown; +import gwt.material.design.client.ui.MaterialLink; +import gwt.material.design.client.ui.MaterialLoader; +import gwt.material.design.client.ui.MaterialToast; + +public class AttachmentMenu extends Composite { + + private static AttachmentMenuUiBinder uiBinder = GWT.create(AttachmentMenuUiBinder.class); + + interface AttachmentMenuUiBinder extends UiBinder { + } + + private enum DownloadStatus { FAILED, IN_PROGRESS, COMPLETE }; + + @UiField MaterialDropDown menu; + @UiField MaterialLink downloadButton; + @UiField MaterialLink saveWSButton; + + private FileModel item; + private MaterialButton parentButton; + private MessageServiceAsync convService; + private DownloadStatus generatingDownloadURL = DownloadStatus.IN_PROGRESS; + + public AttachmentMenu(MessageServiceAsync convService, MaterialButton parent, FileModel item) { + initWidget(uiBinder.createAndBindUi(this)); + this.item = item; + this.parentButton = parent; + this.convService = convService; + convService.getAttachmentDownloadURL(item.getIdentifier(), new AsyncCallback() { + @Override + public void onSuccess(String result) { + if (result != null) { + downloadButton.setHref(result); + generatingDownloadURL = DownloadStatus.COMPLETE; + } + else { + generatingDownloadURL = DownloadStatus.FAILED; + } + } + @Override + public void onFailure(Throwable caught) { + GWT.log("onFailure"); + generatingDownloadURL = DownloadStatus.FAILED; + } + }); + } + + public void setSeparator(boolean separator) { + menu.setSeparator(separator); + } + + public void setActivator(String activator) { + menu.setActivator(activator); + } + + @UiHandler("downloadButton") + void onFileDownload(ClickEvent e) { + switch (generatingDownloadURL) { + case IN_PROGRESS: + e.stopPropagation(); + MaterialToast.fireToast("Generating link is in progress, please retry in few seconds", "rounded"); + break; + case COMPLETE: + MaterialToast.fireToast("Download in progress ... ", "rounded"); + break; + case FAILED: + e.stopPropagation(); + downloadButton.setHref("#"); + MaterialToast.fireToast("Warning: could not generate a download link, some error occurred in the server", "rounded"); + + break; + } + } + + @UiHandler("saveWSButton") + void onSave2WS(ClickEvent e) { + final WorkspaceExplorerSaveDialog navigator = new WorkspaceExplorerSaveDialog(item.getName(), true); + navigator.addStyleName("BS-Navigator");; + navigator.getElement().getStyle().setLeft(50, Unit.PCT); + WorskpaceExplorerSaveNotificationListener listener = new WorskpaceExplorerSaveNotificationListener(){ + + @Override + public void onSaving(Item parent, String fileName) { + GWT.log("onSaving parent: "+parent +", fileName" +fileName); + navigator.hide(); + MaterialLoader.showProgress(true, parentButton); + convService.saveAttachmentToWorkspaceFolder(item.getIdentifier(), parent.getId(), new AsyncCallback() { + @Override + public void onFailure(Throwable caught) { + MaterialLoader.showProgress(false, parentButton); + MaterialToast.fireToast("Warning: could not save this file to the selected Workspace folder, please retry", "rounded"); + } + @Override + public void onSuccess(Boolean result) { + MaterialLoader.showProgress(false, parentButton); + MaterialToast.fireToast("File " +fileName+" Saved succesfully"); + } + }); + } + + @Override + public void onAborted() { + GWT.log("onAborted"); + } + + @Override + public void onFailed(Throwable throwable) { + GWT.log("onFailed"); + } + }; + + navigator.addWorkspaceExplorerSaveNotificationListener(listener); + navigator.show(); + } +} diff --git a/src/main/java/org/gcube/portets/user/message_conversations/client/ui/AttachmentMenu.ui.xml b/src/main/java/org/gcube/portets/user/message_conversations/client/ui/AttachmentMenu.ui.xml new file mode 100644 index 0000000..dccc9fe --- /dev/null +++ b/src/main/java/org/gcube/portets/user/message_conversations/client/ui/AttachmentMenu.ui.xml @@ -0,0 +1,13 @@ + + + + + + + \ No newline at end of file diff --git a/src/main/java/org/gcube/portets/user/message_conversations/client/ui/DisplayMessage.java b/src/main/java/org/gcube/portets/user/message_conversations/client/ui/DisplayMessage.java new file mode 100644 index 0000000..efa7055 --- /dev/null +++ b/src/main/java/org/gcube/portets/user/message_conversations/client/ui/DisplayMessage.java @@ -0,0 +1,107 @@ +package org.gcube.portets.user.message_conversations.client.ui; + +import org.gcube.portets.user.message_conversations.client.MessageServiceAsync; +import org.gcube.portets.user.message_conversations.client.Utils; +import org.gcube.portets.user.message_conversations.shared.ConvMessage; +import org.gcube.portets.user.message_conversations.shared.FileModel; +import org.gcube.portets.user.message_conversations.shared.MessageUserModel; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.dom.client.Style.TextTransform; +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.uibinder.client.UiField; +import com.google.gwt.uibinder.client.UiHandler; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.Widget; + +import gwt.material.design.client.constants.Color; +import gwt.material.design.client.constants.IconPosition; +import gwt.material.design.client.constants.IconType; +import gwt.material.design.client.constants.WavesType; +import gwt.material.design.client.ui.MaterialButton; +import gwt.material.design.client.ui.MaterialLabel; +import gwt.material.design.client.ui.MaterialPanel; +import gwt.material.design.client.ui.MaterialRow; +import gwt.material.design.client.ui.MaterialTextArea; +import gwt.material.design.client.ui.MaterialTitle; + +public class DisplayMessage extends Composite { + + private static DisplayMessageUiBinder uiBinder = GWT.create(DisplayMessageUiBinder.class); + + interface DisplayMessageUiBinder extends UiBinder { + } + @UiField MaterialPanel mainPanel; + + @UiField MaterialRow messageAttachmentsBody; + @UiField MaterialButton showMessages; + @UiField MaterialTextArea messageContent; + @UiField MaterialTitle messageSender, messageSubject, messageRecipients; + private ApplicationView ap; + private MessageServiceAsync convService; + + public DisplayMessage(MessageServiceAsync convService, ApplicationView ap) { + initWidget(uiBinder.createAndBindUi(this)); + this.convService = convService; + this.ap = ap; + } + + /** + * Display the message in the main panel + * @param m message to display + */ + public void showMessage(ConvMessage m) { + String recipientsLabel = ""; + + for (MessageUserModel r : m.getRecipients()) { + String fullName = (r.getFullName() == null) ? r.getUsername() : r.getFullName(); + recipientsLabel+= fullName+"; "; + } + String fullName = (m.getOwner().getFullName() == null) ? m.getOwner().getUsername() : m.getOwner().getFullName(); + messageSender.setTitle(fullName + ", " + Utils.getFormatteDate(m.getDate())); + messageSubject.setTitle(m.getSubject()); + messageRecipients.setTitle("To: "+recipientsLabel); + messageContent.setText(m.getContent()); + messageRecipients.setVisible(true); + messageContent.setVisible(true); + + messageAttachmentsBody.clear(); + messageAttachmentsBody.setVisible(m.hasAttachments()); + messageAttachmentsBody.add(new MaterialLabel("Attachments: ")); + int i = 0; + for (FileModel item : m.getAttachments()) { + String activator = "item"+i; + String attachmentName = (item.getName().length() > 25) ? item.getName().substring(0, 20) + "..." : item.getName(); + MaterialButton toAdd = new MaterialButton(attachmentName, IconType.ARROW_DROP_DOWN); + toAdd.setTitle(item.getName()); + + AttachmentMenu dd = new AttachmentMenu(convService, toAdd, item); + dd.setActivator(activator); + dd.setSeparator(true); + + toAdd.setMargin(5); + toAdd.setPaddingRight(5); + toAdd.setPaddingLeft(5); + toAdd.setWaves(WavesType.DEFAULT); + toAdd.setActivates(activator); + toAdd.setIconPosition(IconPosition.RIGHT); + toAdd.setBackgroundColor(Color.RED_DARKEN_1); + toAdd.getElement().getStyle().setBackgroundImage("none"); + toAdd.getElement().getStyle().setTextTransform(TextTransform.NONE); + + messageAttachmentsBody.add(toAdd); + messageAttachmentsBody.add(dd); + i = i+1; + } + } + + @UiHandler("showMessages") + void onShowMessages(ClickEvent e) { + ap.showSidePanel(); + } + + MaterialPanel getMainPanel() { + return mainPanel; + } +} diff --git a/src/main/java/org/gcube/portets/user/message_conversations/client/ui/DisplayMessage.ui.xml b/src/main/java/org/gcube/portets/user/message_conversations/client/ui/DisplayMessage.ui.xml new file mode 100644 index 0000000..cd8d5f9 --- /dev/null +++ b/src/main/java/org/gcube/portets/user/message_conversations/client/ui/DisplayMessage.ui.xml @@ -0,0 +1,33 @@ + + + + .animation { + transition: 0.4s all; + -webkit-transition: 0.4s all; + -moz-transition: 0.4s all; + } + + .overflowXHidden { + overflow-x: hidden !important; /* needed in windows */ + } + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/java/org/gcube/portets/user/message_conversations/client/ui/MessageItem.java b/src/main/java/org/gcube/portets/user/message_conversations/client/ui/MessageItem.java new file mode 100644 index 0000000..76d01f0 --- /dev/null +++ b/src/main/java/org/gcube/portets/user/message_conversations/client/ui/MessageItem.java @@ -0,0 +1,195 @@ +package org.gcube.portets.user.message_conversations.client.ui; + +import org.gcube.portets.user.message_conversations.client.Utils; +import org.gcube.portets.user.message_conversations.client.event.DeleteMessageEvent; +import org.gcube.portets.user.message_conversations.client.ui.resources.MessagesResources; +import org.gcube.portets.user.message_conversations.shared.ConvMessage; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.dom.client.Style.Cursor; +import com.google.gwt.dom.client.Style.Unit; +import com.google.gwt.dom.client.Style.Visibility; +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.ClickHandler; +import com.google.gwt.event.dom.client.MouseOutEvent; +import com.google.gwt.event.dom.client.MouseOutHandler; +import com.google.gwt.event.dom.client.MouseOverEvent; +import com.google.gwt.event.dom.client.MouseOverHandler; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.uibinder.client.UiField; +import com.google.gwt.uibinder.client.UiHandler; +import com.google.gwt.user.client.Random; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.Widget; + +import gwt.material.design.client.constants.Color; +import gwt.material.design.client.ui.MaterialCollection; +import gwt.material.design.client.ui.MaterialCollectionItem; +import gwt.material.design.client.ui.MaterialDropDown; +import gwt.material.design.client.ui.MaterialIcon; +import gwt.material.design.client.ui.MaterialImage; +import gwt.material.design.client.ui.MaterialLabel; +import gwt.material.design.client.ui.MaterialLink; +import gwt.material.design.client.ui.MaterialToast; + +public class MessageItem extends Composite { + + private static MessageItemUiBinder uiBinder = GWT.create(MessageItemUiBinder.class); + + interface MessageItemUiBinder extends UiBinder { + } + + private final static Color HOVER_MESSAGE_COLOR = Color.GREY_LIGHTEN_4; + + private boolean deleteClicked = true; + + private ApplicationView ap; + private MaterialCollection parentCollection; + private ConvMessage myMessage; + private boolean sent; + + + @UiField MaterialCollectionItem item; + @UiField MaterialImage avatarImage; + @UiField MaterialLabel senderNameLabel; + @UiField MaterialLabel subjectLabel; + @UiField MaterialLabel previewLabel; + @UiField MaterialLabel timeLabel; + @UiField MaterialIcon attachmentsIcon; + @UiField MaterialIcon messageActionIcon; + + @UiField MaterialLink deleteButton; + @UiField MaterialDropDown dd; + + MessagesResources images = GWT.create(MessagesResources.class); + + + public MessageItem(final ConvMessage m, MaterialCollection parentCollection, ApplicationView ap, boolean sent) { + initWidget(uiBinder.createAndBindUi(this)); + item.getElement().getStyle().setCursor(Cursor.POINTER); + this.sent = sent; + this.myMessage = m; + this.ap = ap; + this.parentCollection = parentCollection; + if (m.hasAttachments()) + attachmentsIcon.setVisibility(Visibility.VISIBLE); + if (!m.isRead()) + item.addStyleName("unread-message"); + if (m.getOwner().getAvatarURL() == null || m.getOwner().getAvatarURL().compareTo("")== 0) { + if (sent) + avatarImage.setResource( + m.getRecipients().size() > 1 ? images.group() : images.user()); + else { + avatarImage.setResource(images.user()); + } + } + else { + if (sent && m.getRecipients().size() > 1) + avatarImage.setResource(images.group()); + else + avatarImage.setUrl(m.getOwner().getAvatarURL()); + } + String fullName = (m.getOwner().getFullName() == null) ? m.getOwner().getUsername() : m.getOwner().getFullName(); + senderNameLabel.setText(fullName); + subjectLabel.setText(m.getSubject()); + previewLabel.setText(m.getContent()); + timeLabel.setText(Utils.getFormatteDate(m.getDate())); + + if (sent) + item.setBackgroundColor(Color.WHITE); + + //because on tablet and on mobile cause problems + if (!Utils.isMobile()) { + item.addMouseOverHandler(new MouseOverHandler() { + @Override + public void onMouseOver(MouseOverEvent event) { + messageActionIcon.setVisibility(Visibility.VISIBLE); + item.setBackgroundColor(HOVER_MESSAGE_COLOR); + } + }); + item.addMouseOutHandler(new MouseOutHandler() { + @Override + public void onMouseOut(MouseOutEvent event) { + messageActionIcon.setVisibility(Visibility.HIDDEN); + item.setBackgroundColor(Color.WHITE); + } + }); + } + else {//on mobile + messageActionIcon.setVisibility(Visibility.VISIBLE); + dd.setHoverable(false); + } + messageActionIcon.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + event.stopPropagation(); + } + }); + + deleteButton.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + event.stopPropagation(); + MaterialLink link = new MaterialLink("UNDO"); + link.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + deleteClicked = false; + MaterialToast.fireToast("UNDO DONE"); + item.setVisible(true); + } + }); + new MaterialToast(()->{doDelete(m.getSubject());}, link).toast("Deleting Message ("+m.getSubject()+")"); + item.setVisible(false); + } + }); + dd.getElement().getStyle().setWidth(300, Unit.PX); + String activator = "activate"+Random.nextInt(); + dd.setActivator(activator); + messageActionIcon.setActivates(activator); + } + + + @UiHandler("setUnreadButton") + void onSetUnread(ClickEvent e) { + e.stopPropagation(); + ap.setMessageUnread(myMessage); + myMessage.setRead(false); + item.addStyleName("unread-message"); + } + + private void doDelete(String subject) { + if (deleteClicked) { + GWT.log("is sent?"+ this.sent); + ap.deleteMessage(new DeleteMessageEvent(myMessage, this.sent)); + } + else + deleteClicked = true; + } + + public void setSelected(boolean active) { + item.setBackgroundColor(Color.WHITE); + item.removeStyleName("unread-message"); + item.setActive(active); + myMessage.setRead(true); + } + + @UiHandler("item") + void onClickedMessage(ClickEvent e) { + checkHideSideBarOnMobile(); + parentCollection.clearActive(); + ap.readUserMessage(myMessage.getId(), this.sent); + setSelected(true); + myMessage.setRead(true); + } + + private void checkHideSideBarOnMobile() { + if (Utils.isMobile()) + ap.hideSidePanel(); + } + + public void hideMessageMenu() { + messageActionIcon.setVisible(false); + } + +} diff --git a/src/main/java/org/gcube/portets/user/message_conversations/client/ui/MessageItem.ui.xml b/src/main/java/org/gcube/portets/user/message_conversations/client/ui/MessageItem.ui.xml new file mode 100644 index 0000000..f770004 --- /dev/null +++ b/src/main/java/org/gcube/portets/user/message_conversations/client/ui/MessageItem.ui.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/java/org/gcube/portets/user/message_conversations/client/ui/WriteMessage.java b/src/main/java/org/gcube/portets/user/message_conversations/client/ui/WriteMessage.java new file mode 100644 index 0000000..8cd3664 --- /dev/null +++ b/src/main/java/org/gcube/portets/user/message_conversations/client/ui/WriteMessage.java @@ -0,0 +1,338 @@ +package org.gcube.portets.user.message_conversations.client.ui; + +import java.util.ArrayList; +import java.util.List; + +import org.gcube.portal.clientcontext.client.GCubeClientContext; +import org.gcube.portets.user.message_conversations.client.MessageServiceAsync; +import org.gcube.portets.user.message_conversations.client.Utils; +import org.gcube.portets.user.message_conversations.client.autocomplete.MaterialAutoComplete; +import org.gcube.portets.user.message_conversations.client.oracle.UserOracle; +import org.gcube.portets.user.message_conversations.client.oracle.UserSuggestion; +import org.gcube.portets.user.message_conversations.shared.ConvMessage; +import org.gcube.portets.user.message_conversations.shared.MessageUserModel; +import org.gcube.portets.user.message_conversations.shared.WSUser; +import org.gcube.portlets.widgets.wsexplorer.client.notification.WorkspaceExplorerSelectNotification.WorskpaceExplorerSelectNotificationListener; +import org.gcube.portlets.widgets.wsexplorer.client.select.WorkspaceExplorerSelectDialog; +import org.gcube.portlets.widgets.wsexplorer.shared.FilterCriteria; +import org.gcube.portlets.widgets.wsexplorer.shared.Item; +import org.gcube.portlets.widgets.wsexplorer.shared.ItemType; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.dom.client.Style.Unit; +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.uibinder.client.UiField; +import com.google.gwt.uibinder.client.UiHandler; +import com.google.gwt.user.client.Timer; +import com.google.gwt.user.client.Window; +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.SuggestOracle; +import com.google.gwt.user.client.ui.Widget; + +import gwt.material.design.client.constants.Color; +import gwt.material.design.client.constants.IconSize; +import gwt.material.design.client.constants.IconType; +import gwt.material.design.client.ui.MaterialButton; +import gwt.material.design.client.ui.MaterialChip; +import gwt.material.design.client.ui.MaterialIcon; +import gwt.material.design.client.ui.MaterialLabel; +import gwt.material.design.client.ui.MaterialLink; +import gwt.material.design.client.ui.MaterialPanel; +import gwt.material.design.client.ui.MaterialPreLoader; +import gwt.material.design.client.ui.MaterialRow; +import gwt.material.design.client.ui.MaterialTextArea; +import gwt.material.design.client.ui.MaterialTextBox; + +public class WriteMessage extends Composite { + + private static MessageWindowUiBinder uiBinder = GWT.create(MessageWindowUiBinder.class); + + interface MessageWindowUiBinder extends UiBinder { + } + + @UiField MaterialPanel mainPanel; + @UiField MaterialTextBox txtBoxSubject; + // @UiField MaterialWindow modal; + @UiField MaterialAutoComplete acModal; + @UiField MaterialButton btnSendModal, btnCloseModal; + @UiField MaterialPanel modalContent, sendingLoader; + @UiField MaterialRow attachmentsRow; + @UiField MaterialLink attachButton; + @UiField MaterialLabel sendingFeedback; + @UiField MaterialPreLoader sendingSpinner; + @UiField MaterialTextArea txtArea; + + private ApplicationView ap; + private MessageServiceAsync convService; + + public WriteMessage(MessageServiceAsync convService, ApplicationView ap) { + initWidget(uiBinder.createAndBindUi(this)); + this.convService = convService; + this.ap = ap; + UserOracle oracle = new UserOracle(); + oracle.addContacts(getAllUsers()); + acModal.setSuggestions(oracle); + btnSendModal.getElement().getStyle().setBackgroundImage("none"); + } + + MaterialPanel getMainPanel() { + return mainPanel; + } + + public void setIsReply(ConvMessage msg) { + acModal.addItem(new UserSuggestion( + new WSUser( + msg.getOwner().getUsername(), + msg.getOwner().getUsername(), + msg.getOwner().getFullName(), + msg.getOwner().getEmail() + ) + )); + String subject = getReplySubject(msg.getSubject()); + + txtBoxSubject.setText(subject); + txtBoxSubject.setReadOnly(true); + + txtArea.setText(getReplyHeaderMessage(msg)); + focusOnBody(); + } + public void setIsReplyAll(ConvMessage msg) { + setIsReply(msg); + long currUserId = 0; + try { + currUserId = Long.parseLong(GCubeClientContext.getCurrentUserId()); + } catch (Exception e) { + log("Could not read userId client context"); + return; + } + for (MessageUserModel u : msg.getRecipients()) { + if (u.getUserId() != currUserId) + acModal.addItem(new UserSuggestion( + new WSUser( + u.getUserId()+"", + u.getUsername(), + u.getFullName(), + u.getEmail() + ) + )); + } + focusOnBody(); + } + + public void setIsForward(ConvMessage msg) { + String subject = "Fwd: " + msg.getSubject(); + txtBoxSubject.setText(subject); + txtBoxSubject.setReadOnly(true); + txtArea.setText(getForwardHeaderMessage(msg)); + } + + //need to defer this + private void focusOnBody() { + Timer t = new Timer() { + @Override + public void run() { + if (! Utils.isMobile()) { + txtArea.setCursorPos(0); + txtArea.setFocus(true); + txtArea.setActive(true); + } + } + }; + t.schedule(1000); + } + + private String getReplySubject(String subject) { + if (subject != null) { + return subject.startsWith("Re:") ? subject : "Re: " + subject; + } else + return "No subject"; + } + + private String getReplyHeaderMessage(ConvMessage msg) { + String toReturn = "\n\n---\n on " + msg.getDate() + " " + msg.getOwner().getFullName() + " wrote:"; + toReturn += "\n\n"+msg.getContent(); + return toReturn; + } + + private static native void log(String msg) /*-{ + $wnd.console.log(msg); + }-*/; + + private String getForwardHeaderMessage(ConvMessage msg) { + String toReturn = "\n\n---\nBegin forwarded message:"; + toReturn += "\nFrom: " + msg.getOwner().getFullName(); + toReturn += "\nDate: " + msg.getDate(); + toReturn += "\nTo: "; + if (msg.getRecipients() != null) { + for (MessageUserModel recipient : msg.getRecipients()) { + toReturn += recipient.getFullName() + " "; + } + } + toReturn += "\nSubject: " +msg.getSubject(); + toReturn += "\n\n"+msg.getContent(); + return toReturn; + } + + public WriteMessage setFocusOnUsersInput() { + if (!Utils.isMobile()) + acModal.setFocus(true); + return this; + } + + + + //just used in devlopment + private List getAllUsers() { + List toReturn = new ArrayList<>(); + toReturn.add(new WSUser("testing", "username testing", "Andrea testing", "@gmail.com")); + return toReturn; + } + @UiHandler("attachButton") + void onAttach(ClickEvent e) { + List types = new ArrayList<>(); + ItemType[] theTypes = ItemType.values(); + for (int i = 0; i < theTypes.length; i++) { + if (theTypes[i] != ItemType.FOLDER) + types.add(theTypes[i]); + } + FilterCriteria criteria = null; + final WorkspaceExplorerSelectDialog wpTreepopup = new WorkspaceExplorerSelectDialog("Select an item", criteria, types); + wpTreepopup.getElement().getStyle().setLeft(50, Unit.PCT); + wpTreepopup.setZIndex(10010); + WorskpaceExplorerSelectNotificationListener listener = new WorskpaceExplorerSelectNotificationListener() { + @Override + public void onSelectedItem(Item item) { + attachmentsRow.add(getChip(item.getId(), item.getName(), item.isFolder(), true)); + wpTreepopup.hide(); + } + @Override + public void onFailed(Throwable throwable) { + Window.alert("There are networks problem, please check your connection."); + } + @Override + public void onAborted() {} + @Override + public void onNotValidSelection() { + } + }; + + wpTreepopup.addWorkspaceExplorerSelectNotificationListener(listener); + wpTreepopup.show(); + } + + //for the attachments we distinguish between folder and files + private MaterialChip getChip(String itemId, String itemName, boolean isFolder, boolean deletable) { + MaterialChip chip = new MaterialChip(itemName); + if (deletable) + chip.setIconType(IconType.CLOSE); + chip.setMargin(5); + chip.setId(itemId); + if (isFolder) { + chip.setLetter("F"); + chip.setLetterBackgroundColor(Color.AMBER); + chip.setBackgroundColor(Color.YELLOW); + chip.setTextColor(Color.GREY_DARKEN_2); + } else { + chip.setLetter("D"); + chip.setLetterBackgroundColor(Color.RED); + chip.setTextColor(Color.GREY_DARKEN_2); + } + return chip; + } + + + + private ArrayList getSelectedFilesAndFoldersId() { + ArrayList toReturn = new ArrayList<>(); + int n = attachmentsRow.getWidgetCount(); + for (int i = 0; i < n; i++) { + Widget w = attachmentsRow.getWidget(i); + if (w instanceof MaterialChip) { + toReturn.add(((MaterialChip) w).getId()); + } + } + return toReturn; + } + + + @UiHandler("btnSendModal") + void onSendMessage(ClickEvent e) { + + if (getSelectedUsers().isEmpty()) { + acModal.setError("Look empty to me"); + return; + } else { + acModal.reset(); + } + if (txtBoxSubject.getText().isEmpty()) { + txtBoxSubject.setError("Subject is mandatory"); + return; + } + else { + txtBoxSubject.reset(); + } + if (txtArea.getText().isEmpty()) { + txtArea.setError("The body of the message is mandatory"); + return; + } + else { + txtArea.reset(); + } + GWT.log(getSelectedUsers()+""); + GWT.log(getSelectedFilesAndFoldersId()+""); + ArrayList recipientIds = new ArrayList<>(); + for (WSUser u : getSelectedUsers()) { + recipientIds.add(u.getScreenname()); + } + modalContent.setVisible(false); + sendingLoader.setVisible(true); + + btnSendModal.setEnabled(false); + convService.sendToById(recipientIds, getSelectedFilesAndFoldersId(), txtBoxSubject.getText(), txtArea.getText(), new AsyncCallback() { + @Override + public void onFailure(Throwable caught) { + sendingSpinner.removeFromParent(); + sendingFeedback.setText("We're sorry an error occurred! Please try again"); + modalContent.setVisible(true); + btnSendModal.setEnabled(true); + } + @Override + public void onSuccess(Boolean result) { + sendingSpinner.removeFromParent(); + if (result) { + sendingFeedback.setText("Your message has been sent"); + MaterialIcon okIcon = new MaterialIcon(IconType.DONE, Color.GREEN, Color.WHITE); + okIcon.setIconSize(IconSize.MEDIUM); + sendingLoader.add(okIcon); + btnCloseModal.setText("Close"); + } + else + sendingFeedback.setText("We're sorry an error occurred in the server! Please try again"); + } + }); + } + + @UiHandler("btnCloseModal") + void onClose(ClickEvent e) { + if (Utils.isMobile()) + ap.showSidePanel(); + mainPanel.clear(); + } + + public List getSelectedUsers() { + List values = acModal.getValue(); + List users = new ArrayList<>(values.size()); + for(SuggestOracle.Suggestion value : values){ + if(value instanceof UserSuggestion){ + UserSuggestion us = (UserSuggestion) value; + WSUser user = us.getUser(); + users.add(user); + } + } + return users; + } + + +} diff --git a/src/main/java/org/gcube/portets/user/message_conversations/client/ui/WriteMessage.ui.xml b/src/main/java/org/gcube/portets/user/message_conversations/client/ui/WriteMessage.ui.xml new file mode 100644 index 0000000..f487264 --- /dev/null +++ b/src/main/java/org/gcube/portets/user/message_conversations/client/ui/WriteMessage.ui.xml @@ -0,0 +1,58 @@ + + + + .modalTitle { + font-size: 2em; + } + + .alignCenter { + text-align: center; + padding: 50px; + } + .animation { + transition: 0.4s all; + -webkit-transition: 0.4s all; + -moz-transition: 0.4s all; + } + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/java/org/gcube/portets/user/message_conversations/client/ui/resources/MessagesResources.java b/src/main/java/org/gcube/portets/user/message_conversations/client/ui/resources/MessagesResources.java new file mode 100644 index 0000000..b4fcf39 --- /dev/null +++ b/src/main/java/org/gcube/portets/user/message_conversations/client/ui/resources/MessagesResources.java @@ -0,0 +1,19 @@ +package org.gcube.portets.user.message_conversations.client.ui.resources; + +import com.google.gwt.resources.client.ClientBundle; +import com.google.gwt.resources.client.ImageResource; + +public interface MessagesResources extends ClientBundle { + public static final String WELCOME_MESSAGE = "Welcome to your Messages.\nMessages lets you stay connected, organized, and productive at work, at home, and everywhere in between. "+ +"Also, you can easily attach very large files to your messages from Workspace." + +"\n\nThis message will automatically be deleted as soon you'll receive another one."; + + @Source("group.png") + ImageResource group(); + + @Source("user.png") + ImageResource user(); + + @Source("d4science.png") + ImageResource d4scienceTeam(); +} diff --git a/src/main/java/org/gcube/portets/user/message_conversations/client/ui/resources/d4science.png b/src/main/java/org/gcube/portets/user/message_conversations/client/ui/resources/d4science.png new file mode 100644 index 0000000..e784178 Binary files /dev/null and b/src/main/java/org/gcube/portets/user/message_conversations/client/ui/resources/d4science.png differ diff --git a/src/main/java/org/gcube/portets/user/message_conversations/client/ui/resources/group.png b/src/main/java/org/gcube/portets/user/message_conversations/client/ui/resources/group.png new file mode 100644 index 0000000..9747442 Binary files /dev/null and b/src/main/java/org/gcube/portets/user/message_conversations/client/ui/resources/group.png differ diff --git a/src/main/java/org/gcube/portets/user/message_conversations/client/ui/resources/user.png b/src/main/java/org/gcube/portets/user/message_conversations/client/ui/resources/user.png new file mode 100644 index 0000000..f19113e Binary files /dev/null and b/src/main/java/org/gcube/portets/user/message_conversations/client/ui/resources/user.png differ diff --git a/src/main/java/org/gcube/portets/user/message_conversations/server/ConvServiceImpl.java b/src/main/java/org/gcube/portets/user/message_conversations/server/ConvServiceImpl.java new file mode 100644 index 0000000..10e1dde --- /dev/null +++ b/src/main/java/org/gcube/portets/user/message_conversations/server/ConvServiceImpl.java @@ -0,0 +1,514 @@ +package org.gcube.portets.user.message_conversations.server; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.List; + +import javax.servlet.http.HttpServletRequest; + +import org.gcube.applicationsupportlayer.social.ApplicationNotificationsManager; +import org.gcube.applicationsupportlayer.social.NotificationsManager; +import org.gcube.applicationsupportlayer.social.shared.SocialNetworkingSite; +import org.gcube.applicationsupportlayer.social.shared.SocialNetworkingUser; +import org.gcube.common.authorization.library.provider.SecurityTokenProvider; +import org.gcube.common.homelibary.model.items.type.WorkspaceItemType; +import org.gcube.common.homelibrary.home.HomeLibrary; +import org.gcube.common.homelibrary.home.HomeManager; +import org.gcube.common.homelibrary.home.HomeManagerFactory; +import org.gcube.common.homelibrary.home.exceptions.InternalErrorException; +import org.gcube.common.homelibrary.home.workspace.Workspace; +import org.gcube.common.homelibrary.home.workspace.WorkspaceItem; +import org.gcube.common.homelibrary.home.workspace.exceptions.ItemNotFoundException; +import org.gcube.common.homelibrary.home.workspace.folder.FolderItem; +import org.gcube.common.homelibrary.home.workspace.sharing.WorkspaceMessage; +import org.gcube.common.homelibrary.home.workspace.sharing.WorkspaceMessageManager; +import org.gcube.common.portal.PortalContext; +import org.gcube.common.scope.api.ScopeProvider; +import org.gcube.portal.notifications.bean.GenericItemBean; +import org.gcube.portal.notifications.thread.MessageNotificationsThread; +import org.gcube.portets.user.message_conversations.client.MessageService; +import org.gcube.portets.user.message_conversations.shared.ConvMessage; +import org.gcube.portets.user.message_conversations.shared.CurrUserAndPortalUsersWrapper; +import org.gcube.portets.user.message_conversations.shared.FileModel; +import org.gcube.portets.user.message_conversations.shared.MessageUserModel; +import org.gcube.portets.user.message_conversations.shared.WSUser; +import org.gcube.vomanagement.usermanagement.GroupManager; +import org.gcube.vomanagement.usermanagement.UserManager; +import org.gcube.vomanagement.usermanagement.impl.LiferayGroupManager; +import org.gcube.vomanagement.usermanagement.impl.LiferayUserManager; +import org.gcube.vomanagement.usermanagement.model.GCubeUser; +import org.gcube.vomanagement.usermanagement.util.ManagementUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.gwt.user.server.rpc.RemoteServiceServlet; +import com.liferay.portal.kernel.dao.orm.QueryUtil; +import com.liferay.portal.kernel.exception.PortalException; +import com.liferay.portal.kernel.exception.SystemException; +import com.liferay.portal.kernel.util.OrderByComparator; +import com.liferay.portal.kernel.util.OrderByComparatorFactoryUtil; +import com.liferay.portal.model.User; +import com.liferay.portal.service.GroupLocalServiceUtil; +import com.liferay.portal.service.UserLocalServiceUtil; +import com.liferay.portal.util.PortalUtil; +/** + * The server side implementation of the RPC service. + */ +@SuppressWarnings("serial") +public class ConvServiceImpl extends RemoteServiceServlet implements MessageService { + private static final Logger _log = LoggerFactory.getLogger(ConvServiceImpl.class); + + private PortalContext pContext; + private UserManager um; + + public void init() { + um = new LiferayUserManager(); + pContext = PortalContext.getConfiguration(); + } + /** + * + * @return true if you're running into the portal, false if in development + */ + private boolean isWithinPortal() { + try { + UserLocalServiceUtil.getService(); + return true; + } + catch (com.liferay.portal.kernel.bean.BeanLocatorException ex) { + _log.trace("Development Mode ON"); + return false; + } + } + + private GCubeUser getCurrentUser(HttpServletRequest httpServletRequest) { + if (isWithinPortal()) { + try { + long userId = PortalUtil.getUser(httpServletRequest).getUserId(); + long groupId = pContext.getCurrentGroupId(httpServletRequest); + if (GroupLocalServiceUtil.hasUserGroup(userId, groupId)) + return um.getUserById(userId); + else { + _log.error("User not authorised in Group, the logged user id=" + userId + " does not belong to group " + groupId); + return null; + } + } catch (Exception e) { + _log.warn("Could not read user from LR PortalUtil in delegate servlet"); + return null; + } + } else { + return pContext.getCurrentUser(getThreadLocalRequest()); + } + } + + @Override + public ArrayList getMessages(boolean sent) { + ArrayList toReturn = new ArrayList<>(); + try { + GCubeUser user = getCurrentUser(getThreadLocalRequest()); + pContext = PortalContext.getConfiguration(); + _log.debug("*** Reading user = " +user.getFullname()); + String scope = pContext.getCurrentScope(getThreadLocalRequest()); + ScopeProvider.instance.set(scope); + String token = pContext.getCurrentUserToken(scope, user.getUserId()); + SecurityTokenProvider.instance.set(token); + Workspace workspace = null; + try { + HomeManagerFactory factory = HomeLibrary.getHomeManagerFactory(); + HomeManager manager = factory.getHomeManager(); + workspace = manager.getHome().getWorkspace(); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + + List listMessages = (sent) ? + workspace.getWorkspaceMessageManager().getSentMessages() : workspace.getWorkspaceMessageManager().getReceivedMessages(); + _log.debug("Got messages of " + user.getFullname() + " how many? " + listMessages.size()); + //the messages are returned from the oldest to the new one, so we reverse them + Collections.reverse(listMessages); + LiferayUserManager um = new LiferayUserManager(); + + for (WorkspaceMessage m : listMessages) { + String previewBody = m.getBody().length() > 80 ? m.getBody().substring(0, 79) + " ..." : m.getBody(); + MessageUserModel mu = null; + GCubeUser sender = null; + try { + if (sent) { + if (m.getAddresses().size() < 2) { + GCubeUser recipient = um.getUserByUsername(m.getAddresses().get(0)); + mu = new MessageUserModel(recipient.getUserId(), recipient.getUsername(), recipient.getFullname(), recipient.getUserAvatarURL(), "", ""); + } else { + //we have at least 2 recipients + GCubeUser recipient1 = um.getUserByUsername(m.getAddresses().get(0)); + GCubeUser recipient2 = um.getUserByUsername(m.getAddresses().get(1)); + String label2Display = recipient1.getFirstName() + " & " + recipient2.getFirstName(); + if (m.getAddresses().size() > 2) + label2Display += " & ..."; + mu = new MessageUserModel(recipient1.getUserId(), recipient1.getUsername(), label2Display, null, "", ""); + + } + } else { //received message + sender = um.getUserByUsername(m.getSender().getPortalLogin()); + mu = new MessageUserModel(sender.getUserId(), sender.getUsername(), sender.getFullname(), sender.getUserAvatarURL(), "", ""); + } + } catch (Exception ex) { + if (!sent) { + mu = new MessageUserModel(m.getSender().getPortalLogin()); + } else { + mu = new MessageUserModel(m.getAddresses().get(0)); + } + } + if (!sent) { //received messages + boolean hasAttachments = !m.getAttachmentsIds().isEmpty(); + toReturn.add(new ConvMessage( + m.getId(), + m.getSubject(), + mu, + new Date(m.getSendTime().getTimeInMillis()), + previewBody, + m.isRead(), + hasAttachments)); + } else { //sent messages + ArrayList recipients = new ArrayList<>(); + for (String rec : m.getAddresses()) { + recipients.add(new MessageUserModel(rec)); + } + toReturn.add(new ConvMessage( + m.getId(), + m.getSubject(), + mu, + recipients, + new Date(m.getSendTime().getTimeInMillis()), + previewBody, + m.isRead())); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + _log.trace("get All Messages Received "); + return toReturn; + } + + @Override + public boolean markMessageUnread(String messageId) { + GCubeUser user = getCurrentUser(getThreadLocalRequest()); + String scope = pContext.getCurrentScope(getThreadLocalRequest()); + SecurityTokenProvider.instance.set(pContext.getCurrentUserToken(scope, user.getUserId())); + try { + HomeManagerFactory factory = HomeLibrary.getHomeManagerFactory(); + HomeManager manager = factory.getHomeManager(); + Workspace workspace = manager.getHome().getWorkspace(); + workspace.getWorkspaceMessageManager().getReceivedMessage(messageId).setStatus(false); + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + @Override + public ConvMessage getMessageById(String messageId, boolean sent) { + ConvMessage toReturn = null; + try { + GCubeUser user = getCurrentUser(getThreadLocalRequest()); + _log.debug("*** Reading user from liferay session = " +user.getFullname()); + String scope = pContext.getCurrentScope(getThreadLocalRequest()); + SecurityTokenProvider.instance.set(pContext.getCurrentUserToken(scope, user.getUserId())); + LiferayUserManager um = new LiferayUserManager(); + HomeManagerFactory factory = HomeLibrary.getHomeManagerFactory(); + HomeManager manager = factory.getHomeManager(); + Workspace workspace = manager.getHome().getWorkspace(); + WorkspaceMessage m = (sent) ? + workspace.getWorkspaceMessageManager().getSentMessage(messageId): workspace.getWorkspaceMessageManager().getReceivedMessage(messageId); + MessageUserModel mu = null; + GCubeUser sender = null; + try { + sender = um.getUserByUsername(m.getSender().getPortalLogin()); + mu = new MessageUserModel(sender.getUserId(), sender.getUsername(), sender.getFullname(), extractDomainFromEmail(sender.getEmail())); + } catch (Exception ex) { + mu = new MessageUserModel(m.getSender().getPortalLogin()); + } + ArrayList recipients = new ArrayList<>(); + for (String recipient : m.getAddresses()) { + try { + GCubeUser toAdd = um.getUserByUsername(recipient); + recipients.add(new MessageUserModel(toAdd.getUserId(), toAdd.getUsername(), toAdd.getFullname(), extractDomainFromEmail(toAdd.getEmail()))); + } + catch (Exception ex) { + recipients.add(new MessageUserModel(recipient)); + } + } + + ArrayList attachments = new ArrayList<>(); + List attachItems = m.getAttachmentsIds(); + for (String itemId : attachItems) { + WorkspaceItem item = workspace.getItem(itemId); + String downloadURL = null; //removed for performanc issue and done on demand + attachments.add(new FileModel(item.getId(), item.getName(), null, item.isFolder(), downloadURL)); + } + boolean hasAttachments = !attachItems.isEmpty(); + toReturn = new ConvMessage( + m.getId(), + m.getSubject(), + mu, + recipients, + new Date(m.getSendTime().getTimeInMillis()), + m.getBody(), + m.isRead(), + attachments, + hasAttachments); + if (!sent) + m.setStatus(true); //marked as read + } catch (Exception e) { + e.printStackTrace(); + } + + return toReturn; + } + + @Override + public String getAttachmentDownloadURL(String itemId) { + GCubeUser user = getCurrentUser(getThreadLocalRequest()); + String scope = pContext.getCurrentScope(getThreadLocalRequest()); + SecurityTokenProvider.instance.set(pContext.getCurrentUserToken(scope, user.getUserId())); + try { + HomeManagerFactory factory = HomeLibrary.getHomeManagerFactory(); + HomeManager manager = factory.getHomeManager(); + Workspace workspace = manager.getHome().getWorkspace(); + WorkspaceItem item = workspace.getItem(itemId); + String downladURL = item.getPublicLink(false); + downladURL = (downladURL.startsWith("https")) ? downladURL : downladURL.replace("http", "https"); + return downladURL; + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + @Override + public boolean saveAttachmentToWorkspaceFolder(String itemId, String destinationFolderId) { + GCubeUser user = getCurrentUser(getThreadLocalRequest()); + String scope = pContext.getCurrentScope(getThreadLocalRequest()); + SecurityTokenProvider.instance.set(pContext.getCurrentUserToken(scope, user.getUserId())); + try { + HomeManagerFactory factory = HomeLibrary.getHomeManagerFactory(); + HomeManager manager = factory.getHomeManager(); + Workspace workspace = manager.getHome().getWorkspace(); + WorkspaceItem copied = workspace.copy(itemId, destinationFolderId); + return (copied != null); + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + @Override + public boolean deleteMessageById(String messageId, boolean sent) { + GCubeUser user = getCurrentUser(getThreadLocalRequest()); + _log.debug("deleteMessageById reading user from liferay session = " +user.getFullname() + " m id = " + messageId); + String scope = pContext.getCurrentScope(getThreadLocalRequest()); + SecurityTokenProvider.instance.set(pContext.getCurrentUserToken(scope, user.getUserId())); + try { + HomeManagerFactory factory = HomeLibrary.getHomeManagerFactory(); + HomeManager manager = factory.getHomeManager(); + WorkspaceMessageManager workspaceMessanger = manager.getHome().getWorkspace().getWorkspaceMessageManager(); + if (sent) + workspaceMessanger.deleteSentMessage(messageId); + else { + workspaceMessanger.deleteReceivedMessage(messageId); + } + } catch (Exception e) { + e.printStackTrace(); + return false; + } + return true; + } + + + @Override + public ArrayList searchUsers(String keyword) { + ArrayList toReturn = new ArrayList<>(); + if (isWithinPortal()) { + OrderByComparator comparator = OrderByComparatorFactoryUtil.create("User_", "screenname", true); + try { + _log.debug("Searching " + keyword); + List lrUsers = UserLocalServiceUtil.search(ManagementUtils.getCompany().getCompanyId(), keyword, 0, null, QueryUtil.ALL_POS, QueryUtil.ALL_POS, comparator); + for (User user : lrUsers) { + toReturn.add(new WSUser(""+user.getUserId(), user.getScreenName(), user.getFullName(), extractDomainFromEmail(user.getEmailAddress()))); + } + } catch (SystemException | PortalException e) { + e.printStackTrace(); + } + } else { //development + for (int i = 0; i < 10; i++) { + toReturn.add(new WSUser("andrea.rossi", "andrea.rossi", "Andrea Rossi", "m.assante@gmail.com")); + if (i % 2 == 0) + toReturn.add(new WSUser(""+i, "username"+i, "userGetFullname()"+i, "user.getEmail()"+i)); + else + toReturn.add(new WSUser(""+i, "ciccio"+i, "ciccioNome"+i, "ciccioEMail"+i)); + } + } + return toReturn; + } + + @Override + public boolean sendToById(ArrayList recipientIds, ArrayList listAttachmentsId, String subject, String body) { + PortalContext pContext = PortalContext.getConfiguration(); + GCubeUser currentUser = pContext.getCurrentUser(getThreadLocalRequest()); + + if (listAttachmentsId == null) + listAttachmentsId = new ArrayList(); + try { + Workspace workspace = HomeLibrary.getUserWorkspace(currentUser.getUsername()); + _log.info("Sending message to: " + recipientIds.toString()); + String checkedSubject = subject; + String checkedBody = body; + String messageId = workspace.getWorkspaceMessageManager().sendMessageToPortalLogins(checkedSubject, checkedBody, listAttachmentsId, recipientIds); + try { + body += getPublicLinksForAttachs(workspace, listAttachmentsId); + } + catch (InternalErrorException|ItemNotFoundException e) { + _log.error("Ops, could not generate publick link for some of the attachments"); + } + _log.debug("Sending message notification to: " + recipientIds.toString()); + + List recipients = getUsersbyUserId(recipientIds); + + NotificationsManager nm = new ApplicationNotificationsManager(new SocialNetworkingSite(getThreadLocalRequest()), pContext.getCurrentScope(getThreadLocalRequest()), + new SocialNetworkingUser( + currentUser.getUsername(), + currentUser.getEmail(), + currentUser.getFullname(), + currentUser.getUserAvatarURL() + )); + Thread thread = new Thread(new MessageNotificationsThread(recipients, messageId, checkedSubject, body, nm)); + thread.start(); + + return (messageId != null); + } catch (Exception e) { + _log.error("While Sending message to: " + recipientIds.toString()); + e.printStackTrace(); + return false; + } + } + /** + * utility method extract the @domain.com from an email address + * return @unknown-domain in case of no emails + */ + private String extractDomainFromEmail(String email) { + int index = email.indexOf('@'); + if (index > 0) + return email.substring(index); + else + return "@unknown-domain"; + } + /** + * return the User instance given his id + * @param recipientIds + * @return + */ + private List getUsersbyUserId(List recipientIds) { + List recipients = new ArrayList(); + for (String userid : recipientIds) { + GCubeUser user = null; + try { + user = new LiferayUserManager().getUserByUsername(userid); + recipients.add(new GenericItemBean(""+user.getUserId(), user.getUsername(), user.getFullname(), "")); + } catch (Exception e) { + e.printStackTrace(); + } + } + return recipients; + } + /** + * + * @param workspace + * @param listAttachmentsId + * @return + * @throws ItemNotFoundException + * @throws InternalErrorException + */ + private String getPublicLinksForAttachs(Workspace workspace, ArrayList listAttachmentsId) throws ItemNotFoundException, InternalErrorException{ + + if (listAttachmentsId != null && (!listAttachmentsId.isEmpty()) ) { + List attachments = new ArrayList(); + for (String itemId : listAttachmentsId) { + attachments.add(workspace.getItem(itemId)); + } + + StringBuilder builder = new StringBuilder(); + + if(attachments!=null && attachments.size() > 0){ + builder.append("\n\n\nThe following "); + String msg = attachments.size()>1?"files were attached to this message:":"file was attached to this message:"; + builder.append(msg+"\n"); + for (WorkspaceItem workspaceItem : attachments) { + + if(workspaceItem.getType().equals(WorkspaceItemType.FOLDER_ITEM)) { + FolderItem folderItem = (FolderItem) workspaceItem; + String publicLink = ""; + String itemName = ""; + try { + itemName = workspaceItem.getName(); + publicLink = folderItem.getPublicLink(true); + } + catch (InternalErrorException e) { + _log.warn("An error occurred when creating public link for attachment, skipping file: " + itemName); + return ""; + } + builder.append(itemName + " ("+publicLink+")"); + builder.append("\n"); + } + } + _log.debug("returning public links: "+builder.toString()); + return builder.toString(); + } + else return ""; + } + else return ""; + } + + + /** + * + * @return the list of workspace users + */ + @Override + public CurrUserAndPortalUsersWrapper getWorkspaceUsers() { + + PortalContext pContext = PortalContext.getConfiguration(); + GCubeUser currentUser = pContext.getCurrentUser(getThreadLocalRequest()); + _log.debug("trying to get WorkspaceUsers .."); + WSUser currUser = null; + ArrayList portalUsers = new ArrayList(); + + try { + if (isWithinPortal()) { + UserManager um = new LiferayUserManager(); + GroupManager gm = new LiferayGroupManager(); + List users = um.listUsersByGroup(gm.getRootVO().getGroupId()); + for (GCubeUser user : users) { + _log.trace("Trying to get additional info for "+user.getUsername()); + portalUsers.add(new WSUser(user.getUserId()+"", user.getUsername(), user.getFullname(), user.getEmail())); + + } + } else { + for (int i = 0; i < 10; i++) { + portalUsers.add(new WSUser(""+i, "username"+i, "userGetFullname()"+i, "user.getEmail()"+i)); + } + } + currUser = new WSUser(currentUser.getUsername(), currentUser.getUsername(), currentUser.getFullname(), currentUser.getEmail()); + + + } catch (Exception e) { + _log.error("Error in server get all contacts ", e); + } + CurrUserAndPortalUsersWrapper toReturn = new CurrUserAndPortalUsersWrapper(currUser, portalUsers); + return toReturn; + } + + + + +} diff --git a/src/main/java/org/gcube/portets/user/message_conversations/server/portlet/MessagesPortlet.java b/src/main/java/org/gcube/portets/user/message_conversations/server/portlet/MessagesPortlet.java new file mode 100644 index 0000000..e8fb074 --- /dev/null +++ b/src/main/java/org/gcube/portets/user/message_conversations/server/portlet/MessagesPortlet.java @@ -0,0 +1,44 @@ +/** + * + */ +package org.gcube.portets.user.message_conversations.server.portlet; + +import java.io.IOException; + +import javax.portlet.GenericPortlet; +import javax.portlet.PortletException; +import javax.portlet.PortletRequestDispatcher; +import javax.portlet.RenderRequest; +import javax.portlet.RenderResponse; + + +/** + * + * @author Francesco Mangiacrapa francesco.mangiacrapa@isti.cnr.it + * + */ +public class MessagesPortlet extends GenericPortlet { + + + + /** + * JSP folder name + */ + public static final String JSP_FOLDER = "/WEB-INF/jsp/"; + + /** + * + */ + public static final String VIEW_JSP = JSP_FOLDER + "Messages_view.jsp"; + + /** + * @param request . + * @param response . + * @throws IOException . + * @throws PortletException . + */ + public void doView(RenderRequest request, RenderResponse response) throws PortletException, IOException { + PortletRequestDispatcher rd = getPortletContext().getRequestDispatcher(VIEW_JSP); + rd.include(request,response); + } +} diff --git a/src/main/java/org/gcube/portets/user/message_conversations/shared/ConvMessage.java b/src/main/java/org/gcube/portets/user/message_conversations/shared/ConvMessage.java new file mode 100644 index 0000000..1442e1b --- /dev/null +++ b/src/main/java/org/gcube/portets/user/message_conversations/shared/ConvMessage.java @@ -0,0 +1,163 @@ +package org.gcube.portets.user.message_conversations.shared; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import com.google.gwt.user.client.rpc.IsSerializable; + + +public class ConvMessage implements IsSerializable{ + private String id; + private String subject; + private MessageUserModel owner; + private List recipients; + private Date date; + private String content; + private boolean isRead; + private ArrayList attachments; + private boolean hasAttachments; + + public ConvMessage() { + super(); + } + + + //full constructors + public ConvMessage(String id, String subject, MessageUserModel owner, List recipients, + Date date, String content, boolean isRead, ArrayList attachments, boolean hasAttachments) { + super(); + this.id = id; + this.subject = subject; + this.owner = owner; + this.recipients = recipients; + this.date = date; + this.content = content; + this.isRead = isRead; + this.attachments = attachments; + this.hasAttachments = hasAttachments; + } + + + + //without recipients + public ConvMessage(String id, String subject, MessageUserModel owner, Date date, + String content, boolean isRead, boolean hasAttachments) { + super(); + this.id = id; + this.subject = subject; + this.owner = owner; + this.recipients = null; + this.date = date; + this.content = content; + this.isRead = isRead; + this.hasAttachments = hasAttachments; + + } + + //without attchment + public ConvMessage(String id, String subject, MessageUserModel owner, List recipients, Date date, + String content, boolean isRead) { + super(); + this.id = id; + this.subject = subject; + this.owner = owner; + this.recipients = recipients; + this.date = date; + this.content = content; + this.isRead = isRead; + attachments = new ArrayList<>(); + } + + + public List getRecipients() { + return recipients; + } + + public void setRecipients(ArrayList recipients) { + this.recipients = recipients; + } + + + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getSubject() { + return subject; + } + + public void setSubject(String subject) { + this.subject = subject; + } + + public MessageUserModel getOwner() { + return owner; + } + + public void setOwner(MessageUserModel owner) { + this.owner = owner; + } + + public Date getDate() { + return date; + } + + public void setDate(Date date) { + this.date = date; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public boolean isRead() { + return isRead; + } + + public void setRead(boolean isRead) { + this.isRead = isRead; + } + + public ArrayList getAttachments() { + return attachments; + } + + + public void setAttachments(ArrayList attachments) { + this.attachments = attachments; + } + + + public boolean hasAttachments() { + return hasAttachments; + } + + + public void setHasAttachments(boolean hasAttachments) { + this.hasAttachments = hasAttachments; + } + + + @Override + public String toString() { + return "ConvMessage [id=" + id + ", subject=" + subject + ", owner=" + owner + ", recipients=" + recipients + + ", date=" + date + ", content=" + content + ", isRead=" + isRead + ", attachments=" + attachments + + ", hasAttachments=" + hasAttachments + "]"; + } + + + + + + +} diff --git a/src/main/java/org/gcube/portets/user/message_conversations/shared/CurrUserAndPortalUsersWrapper.java b/src/main/java/org/gcube/portets/user/message_conversations/shared/CurrUserAndPortalUsersWrapper.java new file mode 100644 index 0000000..c342a00 --- /dev/null +++ b/src/main/java/org/gcube/portets/user/message_conversations/shared/CurrUserAndPortalUsersWrapper.java @@ -0,0 +1,38 @@ +package org.gcube.portets.user.message_conversations.shared; + +import java.io.Serializable; +import java.util.ArrayList; + + +@SuppressWarnings("serial") +public class CurrUserAndPortalUsersWrapper implements Serializable { + + private WSUser currentUser; + private ArrayList users; + + public CurrUserAndPortalUsersWrapper() { + super(); + } + + public CurrUserAndPortalUsersWrapper(WSUser currentUser, ArrayList users) { + super(); + this.currentUser = currentUser; + this.users = users; + } + + public WSUser getCurrentUser() { + return currentUser; + } + + public void setCurrentUser(WSUser currentUser) { + this.currentUser = currentUser; + } + + public ArrayList getUsers() { + return users; + } + + public void setUsers(ArrayList users) { + this.users = users; + } +} diff --git a/src/main/java/org/gcube/portets/user/message_conversations/shared/FileModel.java b/src/main/java/org/gcube/portets/user/message_conversations/shared/FileModel.java new file mode 100644 index 0000000..bd1e138 --- /dev/null +++ b/src/main/java/org/gcube/portets/user/message_conversations/shared/FileModel.java @@ -0,0 +1,84 @@ +package org.gcube.portets.user.message_conversations.shared; + +import com.google.gwt.user.client.rpc.IsSerializable; + +public class FileModel implements IsSerializable{ + private String identifier, name; + private String parentId; + boolean isDirectory; + private String downloadLink; + + public FileModel() { + super(); + } + + + + public FileModel(String identifier, String name, String parentId, boolean isDirectory, String downloadLink) { + super(); + this.identifier = identifier; + this.name = name; + this.parentId = parentId; + this.isDirectory = isDirectory; + this.downloadLink = downloadLink; + } + + + + public FileModel(String identifier, String name, String parentId, boolean isDirectory) { + super(); + this.identifier = identifier; + this.name = name; + this.parentId = parentId; + this.isDirectory = isDirectory; + } + + public String getIdentifier() { + return identifier; + } + + public void setIdentifier(String identifier) { + this.identifier = identifier; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getParentId() { + return parentId; + } + + public boolean isDirectory() { + return isDirectory; + } + + public void setDirectory(boolean isDirectory) { + this.isDirectory = isDirectory; + } + + public String getDownloadLink() { + return downloadLink; + } + + + public void setDownloadLink(String downloadLink) { + this.downloadLink = downloadLink; + } + + public void setParentId(String parentId) { + this.parentId = parentId; + } + + @Override + public String toString() { + return "FileModel [identifier=" + identifier + ", name=" + name + ", parentId=" + parentId + ", isDirectory=" + + isDirectory + ", downloadLink=" + downloadLink + "]"; + } + + +} diff --git a/src/main/java/org/gcube/portets/user/message_conversations/shared/MessageUserModel.java b/src/main/java/org/gcube/portets/user/message_conversations/shared/MessageUserModel.java new file mode 100644 index 0000000..7ac85d9 --- /dev/null +++ b/src/main/java/org/gcube/portets/user/message_conversations/shared/MessageUserModel.java @@ -0,0 +1,119 @@ +package org.gcube.portets.user.message_conversations.shared; + +import com.google.gwt.user.client.rpc.IsSerializable; + + +public class MessageUserModel implements IsSerializable { + private long userId; + private String username; + private String fullName; + private String avatarURL; + private String accountURL; + private String email; + + //this constructor is used only for development purposes + public MessageUserModel(String username) { + super(); + this.userId = username.length(); + this.username = username; + this.fullName = username.toUpperCase(); + this.email = "test@gmail.com"; + } + + public MessageUserModel() { + super(); + } + + /** + * + * @param userId + * @param username + * @param fullName + * @param email + */ + public MessageUserModel(long userId, String username, String fullName, String email) { + super(); + this.userId = userId; + this.username = username; + this.fullName = fullName; + this.avatarURL = ""; + this.accountURL = ""; + this.email = email; + } + /** + * + * @param userId + * @param username + * @param fullName + * @param avatarURL + * @param accountURL + * @param email + */ + public MessageUserModel(long userId, String username, String fullName, String avatarURL, String accountURL, String email) { + super(); + this.userId = userId; + this.username = username; + this.fullName = fullName; + this.avatarURL = avatarURL; + this.accountURL = accountURL; + this.email = email; + } + + + public long getUserId() { + return userId; + } + + public void setUserId(long userId) { + this.userId = userId; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getFullName() { + return fullName; + } + + public void setFullName(String fullName) { + this.fullName = fullName; + } + + public String getAvatarURL() { + return avatarURL; + } + + public void setAvatarURL(String avatarURL) { + this.avatarURL = avatarURL; + } + + public String getAccountURL() { + return accountURL; + } + + public void setAccountURL(String accountURL) { + this.accountURL = accountURL; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + @Override + public String toString() { + return "MessageUserModel [userId=" + userId + ", username=" + username + ", fullName=" + fullName + + ", avatarURL=" + avatarURL + ", accountURL=" + accountURL + ", email=" + email + "]"; + } + + + +} diff --git a/src/main/java/org/gcube/portets/user/message_conversations/shared/WSUser.java b/src/main/java/org/gcube/portets/user/message_conversations/shared/WSUser.java new file mode 100644 index 0000000..0d93471 --- /dev/null +++ b/src/main/java/org/gcube/portets/user/message_conversations/shared/WSUser.java @@ -0,0 +1,54 @@ +package org.gcube.portets.user.message_conversations.shared; + +import com.google.gwt.user.client.rpc.IsSerializable; +/** + * + * @author Massimiliano Assante, ISTI-CNR + * + */ +@SuppressWarnings("serial") +public class WSUser implements IsSerializable{ + private String id; + private String screenname; + private String fullName; + private String email; + + public WSUser() { + super(); + } + + public WSUser(String id, String screenname, String fullName, String email) { + super(); + this.id = id; + this.screenname = screenname; + this.fullName = fullName; + this.email = email; + } + public String getId() { + return id; + } + public void setId(String id) { + this.id = id; + } + public String getScreenname() { + return screenname; + } + public void setScreenname(String screenname) { + this.screenname = screenname; + } + public String getFullName() { + return fullName; + } + public void setFullName(String fullName) { + this.fullName = fullName; + } + public String getEmail() { + return email; + } + public void setEmail(String email) { + this.email = email; + } + public String toString() { + return "\nid="+id+" \nlogin="+screenname+" \nfullname="+fullName+" \nemail="+email; + } +} diff --git a/src/main/resources/clientlog4j.properties b/src/main/resources/clientlog4j.properties new file mode 100644 index 0000000..9f94ab9 --- /dev/null +++ b/src/main/resources/clientlog4j.properties @@ -0,0 +1,18 @@ +log4j.rootLogger=INFO, A1 +log4j.appender.A1=org.apache.log4j.ConsoleAppender +log4j.appender.A1.layout=org.apache.log4j.PatternLayout + +# Print the date in ISO 8601 format +log4j.appender.A1.layout.ConversionPattern=%d [%t] %-5p %c - %m%n + +# Print only messages of level TRACE or above in the package org.gcube +log4j.logger.org.gcube=DEBUG +log4j.logger.org.gcube.application.framework.core.session=INFO +log4j.logger.org.gcube.common.scope.impl.DefaultScopeProvider=ERROR +log4j.logger.com.netflix.astyanax.connectionpool.impl.CountingConnectionPoolMonitor=ERROR +log4j.logger.org.gcube.common.homelibrary=WARN +log4j.logger.org.gcube.common.authorization=WARN +log4j.logger.org.gcube.common.clients=WARN +log4j.logger.org.gcube.resources.clients=WARN +log4j.logger.org.gcube.contentmanager=WARN +log4j.logger.org.gcube.resources.discovery=WARN \ No newline at end of file diff --git a/src/main/resources/devnext.servicemap b/src/main/resources/devnext.servicemap new file mode 100644 index 0000000..755d7ee --- /dev/null +++ b/src/main/resources/devnext.servicemap @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/main/resources/devsec.servicemap b/src/main/resources/devsec.servicemap new file mode 100644 index 0000000..0b1946c --- /dev/null +++ b/src/main/resources/devsec.servicemap @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/main/resources/gcube.servicemap b/src/main/resources/gcube.servicemap new file mode 100644 index 0000000..f53e2a4 --- /dev/null +++ b/src/main/resources/gcube.servicemap @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/main/resources/org/gcube/portets/user/message_conversations/MessageConversations.gwt.xml b/src/main/resources/org/gcube/portets/user/message_conversations/MessageConversations.gwt.xml new file mode 100644 index 0000000..eb2315d --- /dev/null +++ b/src/main/resources/org/gcube/portets/user/message_conversations/MessageConversations.gwt.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/webapp/MessageConversations.css b/src/main/webapp/MessageConversations.css new file mode 100644 index 0000000..20f90fd --- /dev/null +++ b/src/main/webapp/MessageConversations.css @@ -0,0 +1,46 @@ +.unread-message { + border-left: 3px solid #2196f3; + } + +.collection .collection-item.active { + background-color: #0277bd !important; +} + +.collection .collection-item.active span.black-text { + color: #fff !important; +} + +.collection .collection-item.active span.grey-text { + color: #ccc !important; +} + +.message-metadata > div:not(.empty-state) h4 { + font-size: 2em; + margin-top: 10px !important; + font-weight: 400 !important; +} + +.message-metadata > div.message-sender h4 { + font-size: 2.2em; + font-weight: 500 !important; +} + +.message-metadata > div.message-recipients h4 { + font-size: 1.6em; + color: #555; + padding-bottom: 5px; + border-bottom: 1px solid grey; +} + +.gwt-SuggestBoxPopup .item-selected { + color: #1565c0 !important; + +} +.breadcrumb { + padding: 2px 10px; +} + +div.modal.BS-Navigator.in div.modal-body { + height: 300px; +} + \ No newline at end of file diff --git a/src/main/webapp/MessageConversations.html b/src/main/webapp/MessageConversations.html new file mode 100644 index 0000000..927012e --- /dev/null +++ b/src/main/webapp/MessageConversations.html @@ -0,0 +1,27 @@ + + + + + + + + + + + + + +Messages Web + + + + + + + + +
+ + diff --git a/src/main/webapp/WEB-INF/jsp/Messages_view.jsp b/src/main/webapp/WEB-INF/jsp/Messages_view.jsp new file mode 100644 index 0000000..94ba7eb --- /dev/null +++ b/src/main/webapp/WEB-INF/jsp/Messages_view.jsp @@ -0,0 +1,53 @@ + +<%@ taglib uri="http://java.sun.com/portlet_2_0" prefix="portlet"%> +<%@ page + import="org.gcube.vomanagement.usermanagement.impl.LiferayGroupManager"%> +<%@ page + import="org.gcube.common.authorization.library.provider.UserInfo"%> +<%@ page import="org.gcube.common.portal.PortalContext"%> + + +<% + long groupId = com.liferay.portal.util.PortalUtil.getScopeGroupId(request); + long userId = com.liferay.portal.util.PortalUtil.getUser(request).getUserId(); + + String strURL = response.encodeURL( + request.getContextPath() + "/MessageConversations.html?gid=" + groupId + "&uid=" + userId); +%> + + + + + + + + diff --git a/src/main/webapp/WEB-INF/liferay-display.xml b/src/main/webapp/WEB-INF/liferay-display.xml new file mode 100644 index 0000000..c9803a6 --- /dev/null +++ b/src/main/webapp/WEB-INF/liferay-display.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/liferay-plugin-package.properties b/src/main/webapp/WEB-INF/liferay-plugin-package.properties new file mode 100644 index 0000000..d023d77 --- /dev/null +++ b/src/main/webapp/WEB-INF/liferay-plugin-package.properties @@ -0,0 +1,9 @@ +name=MessageConv +module-group-id=liferay +module-incremental-version=1 +tags= +short-description= +change-log= +page-url=http://www.d4science.org +author=D4Science.org +licenses=EUPL \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/liferay-portlet.xml b/src/main/webapp/WEB-INF/liferay-portlet.xml new file mode 100644 index 0000000..b9e253d --- /dev/null +++ b/src/main/webapp/WEB-INF/liferay-portlet.xml @@ -0,0 +1,28 @@ + + + + + + MessageConv + false + false + false + /MessageConversations.css + + + administrator + Administrator + + + guest + Guest + + + power-user + Power User + + + user + User + + diff --git a/src/main/webapp/WEB-INF/portlet.xml b/src/main/webapp/WEB-INF/portlet.xml new file mode 100644 index 0000000..e3496d5 --- /dev/null +++ b/src/main/webapp/WEB-INF/portlet.xml @@ -0,0 +1,36 @@ + + + + + MessageConv + MessageConv + org.gcube.portets.user.message_conversations.server.portlet.MessagesPortlet + + view-jsp + /view.jsp + + 0 + + text/html + + + MessageConv Sent + MessageConv Sent + MessageConv Sent + + + + portletSetupShowBorders + false + + + + administrator + + + \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000..e30bee5 --- /dev/null +++ b/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,48 @@ + + + + + + convServlet + org.gcube.portets.user.message_conversations.server.ConvServiceImpl + + + + convServlet + /delegate/message-conversations + + + + message-conversations + com.liferay.portal.kernel.servlet.PortalDelegateServlet + + servlet-class + org.gcube.portets.user.message_conversations.server.ConvServiceImpl + + + sub-context + message-conversations + + 1 + + + + + workspaceExplorer + org.gcube.portlets.widgets.wsexplorer.server.WorkspaceExplorerServiceImpl + + + + workspaceExplorer + /MessageConversations/WorkspaceExplorerService + + + + + MessageConversations.html + + + diff --git a/src/main/webapp/js/gcube-context.js b/src/main/webapp/js/gcube-context.js new file mode 100644 index 0000000..4057214 --- /dev/null +++ b/src/main/webapp/js/gcube-context.js @@ -0,0 +1,31 @@ +/* + * Author: Massimiliano Assante, CNR-ISTI + * */ + +/*The following function simply injects the Liferay object field scopeGroupId in the XMLHttpRequest header. + * So that every ajax call performed in the page has those parameters set.*/ +function injectClientContext() { + if (Liferay != null) { + var userId; + var groupId; + if (Liferay.ThemeDisplay.isSignedIn()) { + userId = Liferay.ThemeDisplay.getUserId(); + groupId = Liferay.ThemeDisplay.getScopeGroupId(); + console.log("groupid is = " + groupId); + } + else { + groupId = Liferay.ThemeDisplay.getScopeGroupId(); + //console.log('Not logged in, injecting groupId only'); + } + //attach the 3 header params in all the XHR sends + (function(send) { + XMLHttpRequest.prototype.send = function(data) { + this.setRequestHeader("gcube-userId", userId); + this.setRequestHeader("gcube-vreid", groupId); + this.setRequestHeader("gcube-request-url", location.href); + send.call(this, data); + }; + })(XMLHttpRequest.prototype.send); + } +} + diff --git a/src/main/webapp/js/iframe-context.js b/src/main/webapp/js/iframe-context.js new file mode 100644 index 0000000..b194fbe --- /dev/null +++ b/src/main/webapp/js/iframe-context.js @@ -0,0 +1,33 @@ +/* + * Author: Massimiliano Assante, CNR-ISTI + * */ + +function getParameterByName(name, url) { + if (!url) url = window.location.href; + name = name.replace(/[\[\]]/g, "\\$&"); + var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"), + results = regex.exec(url); + if (!results) return null; + if (!results[2]) return ''; + return decodeURIComponent(results[2].replace(/\+/g, " ")); +} + + + var ThemeDisplayObject = { + userId : getParameterByName('uid'), + groupId : getParameterByName('gid'), + getUserId : function () { + return this.userId + }, + getScopeGroupId : function () { + return this.groupId + }, + isSignedIn : function () { + return true + } + } + + var Liferay = new Object(); + Liferay.ThemeDisplay = ThemeDisplayObject; + + diff --git a/src/main/webapp/test.html b/src/main/webapp/test.html new file mode 100644 index 0000000..19709bb --- /dev/null +++ b/src/main/webapp/test.html @@ -0,0 +1,15 @@ + + + + +Insert title here + + + + +ciao + + + + \ No newline at end of file