// -*- mode: java; c-basic-offset: 2; -*-
// Copyright 2015 MIT, All rights reserved
// This code is unreleased
package com.google.appinventor.client;
import static com.google.appinventor.client.Ode.MESSAGES;
import com.google.appinventor.client.admin.AdminComparators;
import com.google.appinventor.client.output.OdeLog;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.MouseDownEvent;
import com.google.gwt.event.dom.client.MouseDownHandler;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.i18n.client.DateTimeFormat;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.ClickListener;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.DialogBox;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.PasswordTextBox;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import com.google.appinventor.client.widgets.LabeledTextBox;
import com.google.appinventor.shared.rpc.AdminInterfaceException;
import com.google.appinventor.shared.rpc.admin.AdminUser;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* A list of User elements used in the Admin interface
*
* @author jis@mit.edu (Jeffrey I. Schiller)
*/
public class AdminUserList extends Composite {
private enum SortField {
NAME,
VISITED,
}
private enum SortOrder {
ASCENDING,
DESCENDING,
}
// TODO: add these to OdeMessages.java
private final List<AdminUser> adminUsers;
private SortField sortField;
private SortOrder sortOrder;
// UI elements
private final Grid table;
private final Label nameSortIndicator;
private final Label visitedSortIndicator;
// Date Time Formatter
static final DateTimeFormat dateTimeFormat = DateTimeFormat.getMediumDateTimeFormat();
private static class GalleryEnabledHolder {
boolean enabled;
}
private final static GalleryEnabledHolder galleryEnabledHolder = new GalleryEnabledHolder();
// Callback to fill in table with user objects
private final OdeAsyncCallback<List<AdminUser>> searchCallback = new OdeAsyncCallback<List<AdminUser>>(
"Ooops") {
@Override
public void onSuccess(List<AdminUser> newadminUsers) {
// See if it was enabled since we started. We have to do this because
// when the UI for App Inventor (Ode) is setup, the getSystemConfig call
// may not yet have completed. This call tells us whether or not the Gallery
// is enabled. By default it isn't enabled, but it may have been by the time
// the search button is pressed, so we have to redraw stuff.
boolean tmp = Ode.getInstance().getGallerySettings().galleryEnabled();
if (tmp != galleryEnabledHolder.enabled) {
galleryEnabledHolder.enabled = tmp;
setHeaderRow();
}
adminUsers.clear();
for (AdminUser user : newadminUsers) {
adminUsers.add(user);
}
refreshTable(true);
refreshSortIndicators();
}
};
/**
* Creates a new AdminUserList
*/
public AdminUserList() {
adminUsers = new ArrayList<AdminUser>();
sortField = SortField.NAME;
sortOrder = SortOrder.ASCENDING;;
galleryEnabledHolder.enabled = Ode.getInstance().getGallerySettings().galleryEnabled();
// Initialize UI
if (galleryEnabledHolder.enabled) {
table = new Grid(1, 5);
} else {
table = new Grid(1, 4);
}
table.addStyleName("ode-ProjectTable");
table.setWidth("100%");
table.setCellSpacing(0);
nameSortIndicator = new Label("");
visitedSortIndicator = new Label("");
refreshSortIndicators();
setHeaderRow();
HorizontalPanel searchPanel = new HorizontalPanel();
searchPanel.setSpacing(5);
final LabeledTextBox searchText = new LabeledTextBox("Enter Email address (or partial)");
Button searchButton = new Button("Search");
searchPanel.add(searchText);
searchPanel.add(searchButton);
Button addUserButton = new Button("Add User");
addUserButton.addClickListener(new ClickListener() {
@Override
public void onClick(Widget sender) {
addUpdateUserDialog(null);
}
});
searchPanel.add(addUserButton);
searchButton.addClickListener(new ClickListener() {
@Override
public void onClick(Widget sender) {
Ode.getInstance().getAdminInfoService().searchUsers(searchText.getText(), searchCallback);
}
});
VerticalPanel panel = new VerticalPanel();
panel.setWidth("100%");
panel.add(searchPanel);
panel.add(table);
Button dismissButton = new Button("Dismiss");
dismissButton.addClickListener(new ClickListener() {
@Override
public void onClick(Widget sender) {
Ode.getInstance().switchToDesignView();
}
});
panel.add(dismissButton);
initWidget(panel);
}
/**
* Adds the header row to the table.
*
*/
private void setHeaderRow() {
if (galleryEnabledHolder.enabled) {
table.resizeColumns(5); // Number of columns varies based on whether or not
// the Gallery is enabled
} else {
table.resizeColumns(4);
}
table.getRowFormatter().setStyleName(0, "ode-ProjectHeaderRow");
HorizontalPanel emailHeader = new HorizontalPanel();
final Label emailHeaderLabel = new Label("User Email");
int column = 0;
emailHeaderLabel.addStyleName("ode-ProjectHeaderLabel");
emailHeader.add(emailHeaderLabel);
emailHeader.add(nameSortIndicator);
table.setWidget(0, column, emailHeader);
column += 1;
HorizontalPanel uidHeader = new HorizontalPanel();
final Label uidHeaderLabel = new Label("UID");
uidHeaderLabel.addStyleName("ode-ProjectHeaderLabel");
uidHeader.add(uidHeaderLabel);
table.setWidget(0, column++, uidHeader);
HorizontalPanel adminHeader = new HorizontalPanel();
final Label adminHeaderLabel = new Label("isAdmin?");
adminHeaderLabel.addStyleName("ode-ProjectHeaderLabel");
adminHeader.add(adminHeaderLabel);
table.setWidget(0, column++, adminHeader);
if (galleryEnabledHolder.enabled) {
HorizontalPanel moderatorHeader = new HorizontalPanel();
final Label moderatorHeaderLabel = new Label("isModerator?");
moderatorHeaderLabel.addStyleName("ode-ProjectHeaderLabel");
moderatorHeader.add(moderatorHeaderLabel);
table.setWidget(0, column++, moderatorHeader);
}
HorizontalPanel visitedHeader = new HorizontalPanel();
final Label visitedLabel = new Label("Visited");
visitedLabel.addStyleName("ode-ProjectHeaderLabel");
visitedHeader.add(visitedLabel);
visitedHeader.add(visitedSortIndicator);
table.setWidget(0, column++, visitedHeader);
MouseDownHandler mouseDownHandler = new MouseDownHandler() {
@Override
public void onMouseDown(MouseDownEvent e) {
SortField clickedSortField;
if (e.getSource() == emailHeaderLabel || e.getSource() == nameSortIndicator) {
clickedSortField = SortField.NAME;
} else if (e.getSource() == visitedLabel || e.getSource() == visitedSortIndicator) {
clickedSortField = SortField.VISITED;
} else {
return;
}
changeSortOrder(clickedSortField);
}
};
emailHeaderLabel.addMouseDownHandler(mouseDownHandler);
nameSortIndicator.addMouseDownHandler(mouseDownHandler);
visitedLabel.addMouseDownHandler(mouseDownHandler);
visitedSortIndicator.addMouseDownHandler(mouseDownHandler);
}
private void changeSortOrder(SortField clickedSortField) {
if (sortField != clickedSortField) {
sortField = clickedSortField;
sortOrder = SortOrder.ASCENDING;
} else {
if (sortOrder == SortOrder.ASCENDING) {
sortOrder = SortOrder.DESCENDING;
} else {
sortOrder = SortOrder.ASCENDING;
}
}
refreshTable(true);
}
private void refreshSortIndicators() {
String text = (sortOrder == SortOrder.ASCENDING)
? "\u25B2" // up-pointing triangle
: "\u25BC"; // down-pointing triangle
switch (sortField) {
case NAME:
nameSortIndicator.setText(text);
visitedSortIndicator.setText("");
break;
case VISITED:
nameSortIndicator.setText("");
visitedSortIndicator.setText(text);
break;
}
}
private class UserWidgets {
final Label nameLabel;
final Label uidLabel;
final Label visitedLabel;
final Label isAdminLabel;
final Label isModeratorLabel;
private UserWidgets(final AdminUser user) {
nameLabel = new Label(user.getEmail());
nameLabel.addStyleName("ode-ProjectNameLabel");
uidLabel = new Label(user.getId());
Date visited = user.getVisited();
if (visited == null) {
visitedLabel = new Label("<never>");
} else {
visitedLabel = new Label(dateTimeFormat.format(user.getVisited()));
}
boolean isAdmin = user.getIsAdmin();
if (!isAdmin) {
isAdminLabel = new Label("No");
} else {
isAdminLabel = new Label("Yes");
}
if (galleryEnabledHolder.enabled) {
boolean isModerator = user.getIsModerator();
if (!isModerator) {
isModeratorLabel = new Label("No");
} else {
isModeratorLabel = new Label("Yes");
}
} else {
isModeratorLabel = null; // Need to keep the compiler happy
}
nameLabel.addMouseDownHandler(new MouseDownHandler() {
@Override
public void onMouseDown(MouseDownEvent e) {
addUpdateUserDialog(user);
}
});
}
}
// TODO(user): This method was made public so it can be called
// directly from from Ode when the AdminUserList view is selected
// from another view.
public void refreshTable(boolean needToSort) {
if (needToSort) {
// Sort the projects.
Comparator<AdminUser> comparator;
switch (sortField) {
default:
case NAME:
comparator = (sortOrder == SortOrder.ASCENDING)
? AdminComparators.COMPARE_BY_NAME_ASCENDING
: AdminComparators.COMPARE_BY_NAME_DESCENDING;
break;
case VISITED:
comparator = (sortOrder == SortOrder.ASCENDING)
? AdminComparators.COMPARE_BY_VISTED_DATE_ASCENDING
: AdminComparators.COMPARE_BY_VISTED_DATE_DESCENDING;
break;
}
Collections.sort(adminUsers, comparator);
}
refreshSortIndicators();
// Refill the table.
if (galleryEnabledHolder.enabled) {
table.resize(1 + adminUsers.size(), 5);
} else {
table.resize(1 + adminUsers.size(), 4);
}
int row = 1;
for (AdminUser user : adminUsers) {
UserWidgets uw = new UserWidgets(user);
int column = 0;
table.setWidget(row, column++, uw.nameLabel);
table.setWidget(row, column++, uw.uidLabel);
table.setWidget(row, column++, uw.isAdminLabel);
if (galleryEnabledHolder.enabled) {
table.setWidget(row, column++, uw.isModeratorLabel);
}
table.setWidget(row, column++, uw.visitedLabel);
row++;
}
}
private void addUpdateUserDialog(final AdminUser user) {
boolean adding = true;
if (user != null) { // Adding a user
adding = false;
}
final DialogBox dialogBox = new DialogBox(false, true);
dialogBox.setStylePrimaryName("ode-DialogBox");
if (adding) {
dialogBox.setText("Add User");
} else {
dialogBox.setText("Update User");
}
dialogBox.setGlassEnabled(true);
dialogBox.setAnimationEnabled(true);
final HTML message = new HTML("");
message.setStyleName("DialogBox-message");
final FlexTable userInfo = new FlexTable(); // Holds the username and password labels and boxes
final Label userNameLabel = new Label("User Name");
final TextBox userName = new TextBox();
final Label passwordLabel = new Label("Password");
final Label passwordLabel2 = new Label("Password (again)");
final TextBox passwordBox = new TextBox();
// We switch to the ones below if the hidePasswordCheckbox is selected
// otherwise we use the passwordBox above for the password
final PasswordTextBox passwordBox1 = new PasswordTextBox();
final PasswordTextBox passwordBox2 = new PasswordTextBox();
userInfo.setWidget(0, 0, userNameLabel);
userInfo.setWidget(0, 1, userName);
userInfo.setWidget(1, 0, passwordLabel);
userInfo.setWidget(1, 1, passwordBox);
final CheckBox isAdminBox = new CheckBox("Is Admin?");
final CheckBox hidePasswordCheckbox = new CheckBox("Hide Password");
final HorizontalPanel checkboxPanel = new HorizontalPanel();
checkboxPanel.add(isAdminBox);
final CheckBox isModeratorBox = new CheckBox("Is Moderator?");
if (galleryEnabledHolder.enabled) {
checkboxPanel.add(isModeratorBox);
}
checkboxPanel.add(hidePasswordCheckbox);
VerticalPanel vPanel = new VerticalPanel();
vPanel.add(message);
vPanel.add(userInfo);
vPanel.add(checkboxPanel);
HorizontalPanel buttonPanel = new HorizontalPanel();
Button okButton = new Button("OK");
buttonPanel.add(okButton);
hidePasswordCheckbox.addClickListener(new ClickListener() {
@Override
public void onClick(Widget sender) {
if (hidePasswordCheckbox.isChecked()) { // We just asked to mask passwords
userInfo.setWidget(1, 0, passwordLabel);
userInfo.setWidget(1, 1, passwordBox1);
userInfo.setWidget(2, 0, passwordLabel2);
userInfo.setWidget(2, 1, passwordBox2);
} else { // Unchecked, passwords in the clear
userInfo.setWidget(1, 0, passwordLabel);
userInfo.setWidget(1, 1, passwordBox);
userInfo.removeRow(2);
}
}
});
okButton.addClickListener(new ClickListener() {
@Override
public void onClick(Widget sender) {
String password = passwordBox.getText();
if (hidePasswordCheckbox.isChecked()) {
password = passwordBox1.getText();
String checkPassword = passwordBox2.getText();
if (!checkPassword.equals(password)) {
message.setHTML("<font color=red>Passwords do not match.</font>");
return;
}
}
String email = userName.getText();
if (email.equals("")) {
message.setHTML("<font color=red>You Must Supply a user name (email address)</font>");
return;
} else {
// Work!!
AdminUser nuser = user;
if (nuser == null) {
nuser = new AdminUser(null, email, email, false, isAdminBox.isChecked(),
galleryEnabledHolder.enabled? isModeratorBox.isChecked(): false, null);
} else {
nuser.setIsAdmin(isAdminBox.isChecked());
if (galleryEnabledHolder.enabled) {
nuser.setIsModerator(isModeratorBox.isChecked());
}
nuser.setEmail(email);
}
nuser.setPassword(password);
Ode.getInstance().getAdminInfoService().storeUser(nuser,
new OdeAsyncCallback<Void> ("Oops") {
@Override
public void onSuccess(Void v) {
dialogBox.hide();
}
@Override
public void onFailure(Throwable error) {
OdeLog.xlog(error);
if (error instanceof AdminInterfaceException) {
ErrorReporter.reportError(error.getMessage());
} else {
super.onFailure(error);
}
dialogBox.hide();
}
});
}
}
});
Button cancelButton = new Button("Cancel");
buttonPanel.add(cancelButton);
cancelButton.addClickListener(new ClickListener() {
@Override
public void onClick(Widget sender) {
dialogBox.hide();
}
});
vPanel.add(buttonPanel);
dialogBox.setWidget(vPanel);
if (!adding) {
isAdminBox.setChecked(user.getIsAdmin());
isModeratorBox.setChecked(user.getIsModerator());
userName.setText(user.getEmail());
}
// switchUserPanel -- Put up a button to permit us to
// switch to the selected user, but readonly
if (!adding) {
HorizontalPanel switchUserPanel = new HorizontalPanel();
Button switchButton = new Button("Switch to This User");
switchButton.addClickListener(new ClickListener() {
@Override
public void onClick(Widget sender) {
Ode.getInstance().setReadOnly(); // Must make sure we are read only.
// When we call reloadWindow (below) the onClosing
// handler in Ode will be called. It will attempt
// to save the project settings. But by the time we
// return below, we have switched accounts, so the settings
// will be saved in the wrong account(!!). So we set the
// read-only flag now!
Ode.getInstance().getAdminInfoService().switchUser(user, new OdeAsyncCallback<Void>("Oops") {
@Override
public void onSuccess(Void v) {
Ode.getInstance().reloadWindow(false);
}
});
}
});
switchUserPanel.add(switchButton);
vPanel.add(switchUserPanel);
}
dialogBox.center();
dialogBox.show();
}
}