blob: 55f66b7fff6f601d4d746dd9f2dc852f099d8e2a [file] [log] [blame]
// Copyright (C) 2014 The Android Open Source Project
//
// 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.
package com.google.gerrit.client.change;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.changes.ChangeApi;
import com.google.gerrit.client.info.ChangeInfo;
import com.google.gerrit.client.rpc.GerritCallback;
import com.google.gerrit.client.rpc.Natives;
import com.google.gerrit.common.PageLinks;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.JsArrayString;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyDownEvent;
import com.google.gwt.event.dom.client.KeyDownHandler;
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.DOM;
import com.google.gwt.user.client.rpc.StatusCodeException;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.HTMLPanel;
import com.google.gwt.user.client.ui.UIObject;
import com.google.gwtexpui.globalkey.client.NpTextBox;
import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
import java.util.Iterator;
public class Hashtags extends Composite {
interface Binder extends UiBinder<HTMLPanel, Hashtags> {}
private static final int VISIBLE_LENGTH = 55;
private static final Binder uiBinder = GWT.create(Binder.class);
private static final String REMOVE;
private static final String DATA_ID = "data-id";
private boolean canEdit;
static {
REMOVE = DOM.createUniqueId().replace('-', '_');
init(REMOVE);
}
private static final native void init(String r) /*-{
$wnd[r] = $entry(function(e) {
@com.google.gerrit.client.change.Hashtags::onRemove(Lcom/google/gwt/dom/client/NativeEvent;)(e)
});
}-*/;
private static void onRemove(NativeEvent event) {
String hashtags = getDataId(event);
if (hashtags != null) {
final ChangeScreen screen = ChangeScreen.get(event);
ChangeApi.hashtags(screen.getChangeId().get()).post(
PostInput.create(null, hashtags), new GerritCallback<JavaScriptObject>() {
@Override
public void onSuccess(JavaScriptObject result) {
if (screen.isCurrentView()) {
Gerrit.display(PageLinks.toChange(screen.getChangeId()));
}
}
});
}
}
private static String getDataId(NativeEvent event) {
Element e = event.getEventTarget().cast();
while (e != null) {
String v = e.getAttribute(DATA_ID);
if (!v.isEmpty()) {
return v;
}
e = e.getParentElement();
}
return null;
}
@UiField Element hashtagsText;
@UiField Button openForm;
@UiField Element form;
@UiField Element error;
@UiField NpTextBox hashtagTextBox;
private ChangeScreen.Style style;
private Change.Id changeId;
public Hashtags() {
initWidget(uiBinder.createAndBindUi(this));
hashtagTextBox.setVisibleLength(VISIBLE_LENGTH);
hashtagTextBox.addKeyDownHandler(new KeyDownHandler() {
@Override
public void onKeyDown(KeyDownEvent e) {
if (e.getNativeEvent().getKeyCode() == KeyCodes.KEY_ESCAPE) {
onCancel(null);
} else if (e.getNativeEvent().getKeyCode() == KeyCodes.KEY_ENTER) {
onAdd(null);
}
}
});
}
void init(ChangeScreen.Style style){
this.style = style;
}
void set(ChangeInfo info) {
canEdit = info.hasActions() && info.actions().containsKey("hashtags");
this.changeId = info.legacyId();
display(info);
openForm.setVisible(canEdit);
}
@UiHandler("openForm")
void onOpenForm(@SuppressWarnings("unused") ClickEvent e) {
onOpenForm();
}
void onOpenForm() {
UIObject.setVisible(form, true);
UIObject.setVisible(error, false);
openForm.setVisible(false);
hashtagTextBox.setFocus(true);
}
private void display(ChangeInfo info) {
hashtagsText.setInnerSafeHtml(formatHashtags(info));
}
private void display(JsArrayString hashtags) {
hashtagsText.setInnerSafeHtml(formatHashtags(hashtags));
}
private SafeHtmlBuilder formatHashtags(ChangeInfo info) {
if (info.hashtags() != null) {
return formatHashtags(info.hashtags());
}
return new SafeHtmlBuilder();
}
private SafeHtmlBuilder formatHashtags(JsArrayString hashtags) {
SafeHtmlBuilder html = new SafeHtmlBuilder();
Iterator<String> itr = Natives.asList(hashtags).iterator();
while (itr.hasNext()) {
String hashtagName = itr.next();
html.openSpan()
.setAttribute(DATA_ID, hashtagName)
.setStyleName(style.hashtagName())
.openAnchor()
.setAttribute("href",
"#" + PageLinks.toChangeQuery("hashtag:\"" + hashtagName + "\""))
.setAttribute("role", "listitem")
.append("#").append(hashtagName)
.closeAnchor();
if (canEdit) {
html.openElement("button")
.setAttribute("title", "Remove hashtag")
.setAttribute("onclick", REMOVE + "(event)")
.append("×")
.closeElement("button");
}
html.closeSpan();
if (itr.hasNext()) {
html.append(' ');
}
}
return html;
}
@UiHandler("cancel")
void onCancel(@SuppressWarnings("unused") ClickEvent e) {
openForm.setVisible(true);
UIObject.setVisible(form, false);
hashtagTextBox.setFocus(false);
}
@UiHandler("add")
void onAdd(@SuppressWarnings("unused") ClickEvent e) {
String hashtag = hashtagTextBox.getText();
if (!hashtag.isEmpty()) {
addHashtag(hashtag);
}
}
private void addHashtag(final String hashtags) {
ChangeApi.hashtags(changeId.get()).post(
PostInput.create(hashtags, null),
new GerritCallback<JsArrayString>() {
@Override
public void onSuccess(JsArrayString result) {
hashtagTextBox.setEnabled(true);
UIObject.setVisible(error, false);
error.setInnerText("");
hashtagTextBox.setText("");
if (result != null && result.length() > 0) {
updateHashtagList(result);
}
}
@Override
public void onFailure(Throwable err) {
UIObject.setVisible(error, true);
error.setInnerText(err instanceof StatusCodeException
? ((StatusCodeException) err).getEncodedResponse()
: err.getMessage());
hashtagTextBox.setEnabled(true);
}
});
}
protected void updateHashtagList() {
ChangeApi.detail(changeId.get(),
new GerritCallback<ChangeInfo>() {
@Override
public void onSuccess(ChangeInfo result) {
display(result);
}
});
}
protected void updateHashtagList(JsArrayString hashtags){
display(hashtags);
}
public static class PostInput extends JavaScriptObject {
public static PostInput create(String add, String remove) {
PostInput input = createObject().cast();
input.init(toJsArrayString(add), toJsArrayString(remove));
return input;
}
private static JsArrayString toJsArrayString(String commaSeparated){
if (commaSeparated == null || commaSeparated.equals("")) {
return null;
}
JsArrayString array = JsArrayString.createArray().cast();
for (String hashtag : commaSeparated.split(",")){
array.push(hashtag.trim());
}
return array;
}
private native void init(JsArrayString add, JsArrayString remove) /*-{
this.add = add;
this.remove = remove;
}-*/;
protected PostInput() {
}
}
public static class Result extends JavaScriptObject {
public final native JsArrayString hashtags() /*-{ return this.hashtags; }-*/;
public final native String error() /*-{ return this.error; }-*/;
protected Result() {
}
}
}