Show a popup with user information as tooltip on avatar images
Hovering over an avatar image opens a popup that shows information for this user. At the moment only the image, the name and the email address are shown, but this can be extended further. The popup is shown only after a short delay when keeping the mouse over the image (similar to how tooltips appear with a delay). By this it's avoided that popups are unwantedly shown when moving the mouse cursor over the screen. The popup automatically disappears when the mouse cursor is moved away from the avatar image, but the popup stays open if the mouse cursor is moved over the popup to allow text selection for copy & paste or clicking on links in the popup if we add any later. Change-Id: I0c17f8bd9c76a05d2c7562579018a4dac3a337e8
This commit is contained in:
@@ -14,28 +14,49 @@
|
||||
|
||||
package com.google.gerrit.client;
|
||||
|
||||
import com.google.gerrit.client.account.AccountInfo;
|
||||
import com.google.gerrit.client.rpc.RestApi;
|
||||
import com.google.gwt.event.dom.client.ErrorEvent;
|
||||
import com.google.gwt.event.dom.client.ErrorHandler;
|
||||
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.user.client.Timer;
|
||||
import com.google.gwt.user.client.ui.Image;
|
||||
import com.google.gwt.user.client.ui.UIObject;
|
||||
|
||||
public class AvatarImage extends Image {
|
||||
|
||||
/** A default sized avatar image. */
|
||||
public AvatarImage(String email) {
|
||||
this(email, 0);
|
||||
public AvatarImage(AccountInfo account) {
|
||||
this(account, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* An avatar image for the given account using the requested size.
|
||||
*
|
||||
* @param email The email address of the account in which we are interested
|
||||
* @param account The account in which we are interested
|
||||
* @param size A requested size. Note that the size can be ignored depending
|
||||
* on the avatar provider. A size <= 0 indicates to let the provider
|
||||
* decide a default size.
|
||||
*/
|
||||
public AvatarImage(String email, int size) {
|
||||
super(url(email, size));
|
||||
public AvatarImage(AccountInfo account, int size) {
|
||||
this(account, size, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* An avatar image for the given account using the requested size.
|
||||
*
|
||||
* @param account The account in which we are interested
|
||||
* @param size A requested size. Note that the size can be ignored depending
|
||||
* on the avatar provider. A size <= 0 indicates to let the provider
|
||||
* decide a default size.
|
||||
* @param addPopup show avatar popup with user info on hovering over the
|
||||
* avatar image
|
||||
*/
|
||||
public AvatarImage(AccountInfo account, int size, boolean addPopup) {
|
||||
super(url(account.email(), size));
|
||||
|
||||
if (size > 0) {
|
||||
// If the provider does not resize the image, force it in the browser.
|
||||
@@ -50,6 +71,13 @@ public class AvatarImage extends Image {
|
||||
setVisible(false);
|
||||
}
|
||||
});
|
||||
|
||||
if (addPopup) {
|
||||
UserPopupPanel userPopup = new UserPopupPanel(account, false, false);
|
||||
PopupHandler popupHandler = new PopupHandler(userPopup, this);
|
||||
addMouseOverHandler(popupHandler);
|
||||
addMouseOutHandler(popupHandler);
|
||||
}
|
||||
}
|
||||
|
||||
private static String url(String email, int size) {
|
||||
@@ -68,4 +96,79 @@ public class AvatarImage extends Image {
|
||||
}
|
||||
return api.url();
|
||||
}
|
||||
|
||||
private class PopupHandler implements MouseOverHandler, MouseOutHandler {
|
||||
private final UserPopupPanel popup;
|
||||
private final UIObject target;
|
||||
|
||||
private Timer showTimer;
|
||||
private Timer hideTimer;
|
||||
|
||||
public PopupHandler(UserPopupPanel popup, UIObject target) {
|
||||
this.popup = popup;
|
||||
this.target = target;
|
||||
|
||||
popup.addDomHandler(new MouseOverHandler() {
|
||||
@Override
|
||||
public void onMouseOver(MouseOverEvent event) {
|
||||
scheduleShow();
|
||||
}
|
||||
}, MouseOverEvent.getType());
|
||||
popup.addDomHandler(new MouseOutHandler() {
|
||||
@Override
|
||||
public void onMouseOut(MouseOutEvent event) {
|
||||
scheduleHide();
|
||||
}
|
||||
}, MouseOutEvent.getType());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMouseOver(MouseOverEvent event) {
|
||||
scheduleShow();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMouseOut(MouseOutEvent event) {
|
||||
scheduleHide();
|
||||
}
|
||||
|
||||
private void scheduleShow() {
|
||||
if (hideTimer != null) {
|
||||
hideTimer.cancel();
|
||||
hideTimer = null;
|
||||
}
|
||||
if ((popup.isShowing() && popup.isVisible()) || showTimer != null) {
|
||||
return;
|
||||
}
|
||||
showTimer = new Timer() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (!popup.isShowing() || !popup.isVisible()) {
|
||||
popup.showRelativeTo(target);
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
showTimer.schedule(600);
|
||||
}
|
||||
|
||||
private void scheduleHide() {
|
||||
if (showTimer != null) {
|
||||
showTimer.cancel();
|
||||
showTimer = null;
|
||||
}
|
||||
if (!popup.isShowing() || !popup.isVisible() || hideTimer != null) {
|
||||
return;
|
||||
}
|
||||
hideTimer = new Timer() {
|
||||
@Override
|
||||
public void run() {
|
||||
popup.hide();
|
||||
}
|
||||
};
|
||||
hideTimer.schedule(50);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -178,7 +178,7 @@ public class FormatUtil {
|
||||
return nameEmail(ai);
|
||||
}
|
||||
|
||||
private static AccountInfo asInfo(Account acct) {
|
||||
public static AccountInfo asInfo(Account acct) {
|
||||
if (acct == null) {
|
||||
return AccountInfo.create(0, null, null);
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import static com.google.gerrit.common.data.GlobalCapability.CREATE_GROUP;
|
||||
import static com.google.gerrit.common.data.GlobalCapability.CREATE_PROJECT;
|
||||
|
||||
import com.google.gerrit.client.account.AccountCapabilities;
|
||||
import com.google.gerrit.client.account.AccountInfo;
|
||||
import com.google.gerrit.client.admin.ProjectScreen;
|
||||
import com.google.gerrit.client.changes.ChangeConstants;
|
||||
import com.google.gerrit.client.changes.ChangeListScreen;
|
||||
@@ -249,6 +250,11 @@ public class Gerrit implements EntryPoint {
|
||||
return myAccount;
|
||||
}
|
||||
|
||||
/** @return the currently signed in user's account data; empty account data if no account */
|
||||
public static AccountInfo getUserAccountInfo() {
|
||||
return FormatUtil.asInfo(myAccount);
|
||||
}
|
||||
|
||||
/** @return access token to prove user identity during REST API calls. */
|
||||
public static String getXGerritAuth() {
|
||||
return xGerritAuth;
|
||||
@@ -762,9 +768,9 @@ public class Gerrit implements EntryPoint {
|
||||
}
|
||||
|
||||
private static void whoAmI(boolean canLogOut) {
|
||||
Account account = getUserAccount();
|
||||
final CurrentUserPopupPanel userPopup =
|
||||
new CurrentUserPopupPanel(account, canLogOut);
|
||||
AccountInfo account = getUserAccountInfo();
|
||||
final UserPopupPanel userPopup =
|
||||
new UserPopupPanel(account, canLogOut, true);
|
||||
final FlowPanel userSummaryPanel = new FlowPanel();
|
||||
class PopupHandler implements KeyDownHandler, ClickHandler {
|
||||
private void showHidePopup() {
|
||||
@@ -791,7 +797,7 @@ public class Gerrit implements EntryPoint {
|
||||
final PopupHandler popupHandler = new PopupHandler();
|
||||
final InlineLabel l = new InlineLabel(FormatUtil.name(account));
|
||||
l.setStyleName(RESOURCES.css().menuBarUserName());
|
||||
final AvatarImage avatar = new AvatarImage(account.getPreferredEmail(), 26);
|
||||
final AvatarImage avatar = new AvatarImage(account, 26, false);
|
||||
avatar.setStyleName(RESOURCES.css().menuBarUserNameAvatar());
|
||||
userSummaryPanel.setStyleName(RESOURCES.css().menuBarUserNamePanel());
|
||||
userSummaryPanel.add(l);
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
|
||||
package com.google.gerrit.client;
|
||||
|
||||
import com.google.gerrit.client.account.AccountInfo;
|
||||
import com.google.gerrit.common.PageLinks;
|
||||
import com.google.gerrit.reviewdb.client.Account;
|
||||
import com.google.gwt.core.client.GWT;
|
||||
import com.google.gwt.uibinder.client.UiBinder;
|
||||
import com.google.gwt.uibinder.client.UiField;
|
||||
@@ -24,8 +24,8 @@ import com.google.gwt.user.client.ui.Label;
|
||||
import com.google.gwt.user.client.ui.Widget;
|
||||
import com.google.gwtexpui.user.client.PluginSafePopupPanel;
|
||||
|
||||
public class CurrentUserPopupPanel extends PluginSafePopupPanel {
|
||||
interface Binder extends UiBinder<Widget, CurrentUserPopupPanel> {
|
||||
public class UserPopupPanel extends PluginSafePopupPanel {
|
||||
interface Binder extends UiBinder<Widget, UserPopupPanel> {
|
||||
}
|
||||
|
||||
private static final Binder binder = GWT.create(Binder.class);
|
||||
@@ -41,9 +41,10 @@ public class CurrentUserPopupPanel extends PluginSafePopupPanel {
|
||||
@UiField
|
||||
Anchor settings;
|
||||
|
||||
public CurrentUserPopupPanel(Account account, boolean canLogOut) {
|
||||
public UserPopupPanel(AccountInfo account, boolean canLogOut,
|
||||
boolean showSettingsLink) {
|
||||
super(/* auto hide */true, /* modal */false);
|
||||
avatar = new AvatarImage(account.getPreferredEmail(), 100);
|
||||
avatar = new AvatarImage(account, 100, false);
|
||||
setWidget(binder.createAndBindUi(this));
|
||||
// We must show and then hide this popup so that it is part of the DOM.
|
||||
// Otherwise the image does not get any events. Calling hide() would
|
||||
@@ -51,17 +52,21 @@ public class CurrentUserPopupPanel extends PluginSafePopupPanel {
|
||||
show();
|
||||
setVisible(false);
|
||||
setStyleName(Gerrit.RESOURCES.css().userInfoPopup());
|
||||
if (account.getFullName() != null) {
|
||||
userName.setText(account.getFullName());
|
||||
if (account.name() != null) {
|
||||
userName.setText(account.name());
|
||||
}
|
||||
if (account.getPreferredEmail() != null) {
|
||||
userEmail.setText(account.getPreferredEmail());
|
||||
if (account.email() != null) {
|
||||
userEmail.setText(account.email());
|
||||
}
|
||||
if (canLogOut) {
|
||||
logout.setHref(Gerrit.selfRedirect("/logout"));
|
||||
} else {
|
||||
logout.setVisible(false);
|
||||
}
|
||||
settings.setHref(Gerrit.selfRedirect(PageLinks.SETTINGS));
|
||||
if (showSettingsLink) {
|
||||
settings.setHref(Gerrit.selfRedirect(PageLinks.SETTINGS));
|
||||
} else {
|
||||
settings.setVisible(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -360,10 +360,10 @@ public class PublishCommentScreen extends AccountScreen implements
|
||||
final CommentEditorPanel editor =
|
||||
new CommentEditorPanel(c, commentLinkProcessor);
|
||||
if (c.getLine() == AbstractPatchContentTable.R_HEAD) {
|
||||
editor.setAuthorNameText(Gerrit.getUserAccount().getPreferredEmail(),
|
||||
editor.setAuthorNameText(Gerrit.getUserAccountInfo(),
|
||||
Util.C.fileCommentHeader());
|
||||
} else {
|
||||
editor.setAuthorNameText(Gerrit.getUserAccount().getPreferredEmail(),
|
||||
editor.setAuthorNameText(Gerrit.getUserAccountInfo(),
|
||||
Util.M.lineHeader(c.getLine()));
|
||||
}
|
||||
editor.setOpen(true);
|
||||
|
||||
@@ -65,8 +65,7 @@ public class CommentEditorPanel extends CommentPanel implements ClickHandler,
|
||||
comment = plc;
|
||||
|
||||
addStyleName(Gerrit.RESOURCES.css().commentEditorPanel());
|
||||
setAuthorNameText(Gerrit.getUserAccount().getPreferredEmail(),
|
||||
PatchUtil.C.draft());
|
||||
setAuthorNameText(Gerrit.getUserAccountInfo(), PatchUtil.C.draft());
|
||||
setMessageText(plc.getMessage());
|
||||
addDoubleClickHandler(this);
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ public class CommentPanel extends Composite implements HasDoubleClickHandlers,
|
||||
this(commentLinkProcessor);
|
||||
|
||||
setMessageText(message);
|
||||
setAuthorNameText(author.email(), FormatUtil.name(author));
|
||||
setAuthorNameText(author, FormatUtil.name(author));
|
||||
setDateText(FormatUtil.shortFormatDayTime(when));
|
||||
|
||||
final CellFormatter fmt = header.getCellFormatter();
|
||||
@@ -126,8 +126,8 @@ public class CommentPanel extends Composite implements HasDoubleClickHandlers,
|
||||
SafeHtml.set(messageText, buf);
|
||||
}
|
||||
|
||||
public void setAuthorNameText(final String authorEmail, final String nameText) {
|
||||
header.setWidget(0, 0, new AvatarImage(authorEmail, 26));
|
||||
public void setAuthorNameText(final AccountInfo author, final String nameText) {
|
||||
header.setWidget(0, 0, new AvatarImage(author, 26));
|
||||
header.setText(0, 1, nameText);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user