Merge branch 'stable-2.16'

* stable-2.16:
  Add user preferences panel to PolyGerrit UI
  Add PolyGerrit-based screen for uploading images
  Enable display of images in the comments in the new UI
  Format code with GJF-1.7
  Remove GWT UI

Change-Id: I90fb27bf89e673b3fd7321500c86ca4bbb63eaa6
diff --git a/BUILD b/BUILD
index 6adb380..7b71db2 100644
--- a/BUILD
+++ b/BUILD
@@ -3,7 +3,6 @@
 gerrit_plugin(
     name = "imagare",
     srcs = glob(["src/main/java/**/*.java"]),
-    gwt_module = "com.googlesource.gerrit.plugins.imagare.Imagare",
     manifest_entries = [
         "Gerrit-PluginName: imagare",
         "Gerrit-Module: com.googlesource.gerrit.plugins.imagare.Module",
diff --git a/src/main/java/com/googlesource/gerrit/plugins/imagare/HttpModule.java b/src/main/java/com/googlesource/gerrit/plugins/imagare/HttpModule.java
index d719e66..627cfd8 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/imagare/HttpModule.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/imagare/HttpModule.java
@@ -16,7 +16,6 @@
 
 import com.google.gerrit.extensions.annotations.PluginName;
 import com.google.gerrit.extensions.registration.DynamicSet;
-import com.google.gerrit.extensions.webui.GwtPlugin;
 import com.google.gerrit.extensions.webui.JavaScriptPlugin;
 import com.google.gerrit.extensions.webui.WebUiPlugin;
 import com.google.gerrit.httpd.plugins.HttpPluginModule;
@@ -40,7 +39,10 @@
       serveRegex("^" + ImageServlet.PATH_PREFIX + "(.+)?$").with(ImageServlet.class);
     }
 
-    DynamicSet.bind(binder(), WebUiPlugin.class).toInstance(new GwtPlugin("imagare"));
+    // GWT only
     DynamicSet.bind(binder(), WebUiPlugin.class).toInstance(new JavaScriptPlugin("imagare.js"));
+
+    // Polymer only
+    DynamicSet.bind(binder(), WebUiPlugin.class).toInstance(new JavaScriptPlugin("imagare.html"));
   }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/imagare/Imagare.gwt.xml b/src/main/java/com/googlesource/gerrit/plugins/imagare/Imagare.gwt.xml
deleted file mode 100644
index d363446..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/imagare/Imagare.gwt.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
- 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.
--->
-<module rename-to="imagare">
-  <!-- Inherit the core Web Toolkit stuff.                        -->
-  <inherits name="com.google.gwt.user.User"/>
-  <!-- Other module inherits                                      -->
-  <inherits name="com.google.gerrit.Plugin"/>
-  <inherits name="com.google.gwt.http.HTTP"/>
-  <inherits name='com.google.gwtexpui.clippy.Clippy'/>
-  <!-- Specify the app entry point class.                         -->
-  <entry-point class="com.googlesource.gerrit.plugins.imagare.client.ImagarePlugin"/>
-  <stylesheet src="imagare.css"/>
-</module>
diff --git a/src/main/java/com/googlesource/gerrit/plugins/imagare/client/ConfigInfo.java b/src/main/java/com/googlesource/gerrit/plugins/imagare/client/ConfigInfo.java
deleted file mode 100644
index 65d9b96..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/imagare/client/ConfigInfo.java
+++ /dev/null
@@ -1,48 +0,0 @@
-// 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.googlesource.gerrit.plugins.imagare.client;
-
-import com.google.gwt.core.client.JavaScriptObject;
-
-public class ConfigInfo extends JavaScriptObject {
-  final native String getDefaultProject() /*-{ return this.default_project }-*/;
-
-  final LinkDecoration getLinkDecoration() {
-    if (link_decoration() == null) {
-      return LinkDecoration.NONE;
-    }
-    return LinkDecoration.valueOf(link_decoration());
-  }
-
-  private final native String link_decoration() /*-{ return this.link_decoration; }-*/;
-
-  final native boolean stage() /*-{ return this.stage ? true : false; }-*/;
-
-  final native boolean
-      enableImageServer() /*-{ return this.enable_image_server ? true : false; }-*/;
-
-  final native void setDefaultProject(String p) /*-{ this.default_project = p; }-*/;
-
-  final native void setLinkDecoration(String d) /*-{ this.link_decoration = d; }-*/;
-
-  final native void setStage(boolean s) /*-{ this.stage = s; }-*/;
-
-  static ConfigInfo create() {
-    ConfigInfo g = (ConfigInfo) createObject();
-    return g;
-  }
-
-  protected ConfigInfo() {}
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/imagare/client/ConfirmationCallback.java b/src/main/java/com/googlesource/gerrit/plugins/imagare/client/ConfirmationCallback.java
deleted file mode 100644
index 209d14f..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/imagare/client/ConfirmationCallback.java
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright (C) 2010 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.googlesource.gerrit.plugins.imagare.client;
-
-/**
- * Interface that a caller must implement to react on the result of a {@link ConfirmationDialog}.
- */
-public abstract class ConfirmationCallback {
-
-  /**
-   * Called when the {@link ConfirmationDialog} is finished with OK. To be overwritten by
-   * subclasses.
-   */
-  public abstract void onOk();
-
-  /**
-   * Called when the {@link ConfirmationDialog} is finished with Cancel. To be overwritten by
-   * subclasses.
-   */
-  public void onCancel() {}
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/imagare/client/ConfirmationDialog.java b/src/main/java/com/googlesource/gerrit/plugins/imagare/client/ConfirmationDialog.java
deleted file mode 100644
index 062f8df..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/imagare/client/ConfirmationDialog.java
+++ /dev/null
@@ -1,78 +0,0 @@
-// Copyright (C) 2010 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.googlesource.gerrit.plugins.imagare.client;
-
-import com.google.gwt.event.dom.client.ClickEvent;
-import com.google.gwt.event.dom.client.ClickHandler;
-import com.google.gwt.user.client.ui.Button;
-import com.google.gwt.user.client.ui.FlowPanel;
-import com.google.gwt.user.client.ui.Widget;
-import com.google.gwtexpui.safehtml.client.SafeHtml;
-import com.google.gwtexpui.user.client.AutoCenterDialogBox;
-
-public class ConfirmationDialog extends AutoCenterDialogBox {
-
-  private Button cancelButton;
-
-  public ConfirmationDialog(
-      final String dialogTitle, final SafeHtml message, final ConfirmationCallback callback) {
-    super(/* auto hide */ false, /* modal */ true);
-    setGlassEnabled(true);
-    setText(dialogTitle);
-
-    final FlowPanel buttons = new FlowPanel();
-
-    final Button okButton = new Button();
-    okButton.setText("OK");
-    okButton.addClickHandler(
-        new ClickHandler() {
-          @Override
-          public void onClick(ClickEvent event) {
-            hide();
-            callback.onOk();
-          }
-        });
-    buttons.add(okButton);
-
-    cancelButton = new Button();
-    cancelButton.getElement().getStyle().setProperty("marginLeft", "300px");
-    cancelButton.setText("Cancel");
-    cancelButton.addClickHandler(
-        new ClickHandler() {
-          @Override
-          public void onClick(ClickEvent event) {
-            hide();
-            callback.onCancel();
-          }
-        });
-    buttons.add(cancelButton);
-
-    final FlowPanel center = new FlowPanel();
-    final Widget msgWidget = message.toBlockWidget();
-    center.add(msgWidget);
-    center.add(buttons);
-    add(center);
-
-    msgWidget.setWidth("400px");
-
-    setWidget(center);
-  }
-
-  @Override
-  public void center() {
-    super.center();
-    cancelButton.setFocus(true);
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/imagare/client/ImagareAdminScreen.java b/src/main/java/com/googlesource/gerrit/plugins/imagare/client/ImagareAdminScreen.java
deleted file mode 100644
index 78d6945..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/imagare/client/ImagareAdminScreen.java
+++ /dev/null
@@ -1,42 +0,0 @@
-// 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.googlesource.gerrit.plugins.imagare.client;
-
-import com.google.gerrit.plugin.client.Plugin;
-import com.google.gerrit.plugin.client.rpc.RestApi;
-import com.google.gerrit.plugin.client.screen.Screen;
-
-public class ImagareAdminScreen extends ImagareConfigScreen {
-
-  static class Factory implements Screen.EntryPoint {
-    private final boolean enableImageServer;
-
-    Factory(boolean enableImageServer) {
-      this.enableImageServer = enableImageServer;
-    }
-
-    @Override
-    public void onLoad(Screen screen) {
-      screen.setPageTitle(Plugin.get().getName() + "Admin");
-      screen.show(new ImagareAdminScreen(enableImageServer));
-    }
-  }
-
-  ImagareAdminScreen(boolean enableImageServer) {
-    super(
-        enableImageServer,
-        new RestApi("config").id("server").view(Plugin.get().getPluginName(), "config"));
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/imagare/client/ImagareConfigScreen.java b/src/main/java/com/googlesource/gerrit/plugins/imagare/client/ImagareConfigScreen.java
deleted file mode 100644
index cc261c9..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/imagare/client/ImagareConfigScreen.java
+++ /dev/null
@@ -1,156 +0,0 @@
-// 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.googlesource.gerrit.plugins.imagare.client;
-
-import com.google.gerrit.plugin.client.rpc.RestApi;
-import com.google.gwt.core.client.JavaScriptObject;
-import com.google.gwt.event.dom.client.ClickEvent;
-import com.google.gwt.event.dom.client.ClickHandler;
-import com.google.gwt.user.client.rpc.AsyncCallback;
-import com.google.gwt.user.client.ui.Button;
-import com.google.gwt.user.client.ui.CheckBox;
-import com.google.gwt.user.client.ui.HorizontalPanel;
-import com.google.gwt.user.client.ui.Image;
-import com.google.gwt.user.client.ui.Label;
-import com.google.gwt.user.client.ui.ListBox;
-import com.google.gwt.user.client.ui.TextBox;
-import com.google.gwt.user.client.ui.VerticalPanel;
-
-public abstract class ImagareConfigScreen extends VerticalPanel {
-  protected final boolean enableImageServer;
-  private final RestApi restApi;
-
-  private TextBox projectBox;
-  private ListBox linkDecorationBox;
-  private CheckBox stageBox;
-  private Button saveButton;
-
-  protected ImagareConfigScreen(boolean enableImageServer, RestApi restApi) {
-    this.enableImageServer = enableImageServer;
-    this.restApi = restApi;
-    setStyleName("imagare-config-screen");
-    restApi.get(
-        new AsyncCallback<ConfigInfo>() {
-          @Override
-          public void onSuccess(ConfigInfo info) {
-            display(info);
-          }
-
-          @Override
-          public void onFailure(Throwable caught) {
-            // never invoked
-          }
-        });
-  }
-
-  protected void display(ConfigInfo info) {
-    HorizontalPanel p = new HorizontalPanel();
-    p.setStyleName("imagare-label-panel");
-    p.add(new Label("Link Decoration"));
-    Image linkDecorationInfo = new Image(ImagarePlugin.RESOURCES.info());
-    linkDecorationInfo.setTitle(
-        "Decoration for image links in the Gerrit WebUI."
-            + " 'NONE': no decoration, 'TOOLTIP': the image is shown as tooltip on"
-            + " mouse over an image link, 'INLINE': the image is inlined instead of"
-            + " the URL.");
-    p.add(linkDecorationInfo);
-    p.add(new Label(":"));
-    linkDecorationBox = new ListBox();
-    int i = 0;
-    for (LinkDecoration v : LinkDecoration.values()) {
-      linkDecorationBox.addItem(v.name());
-      if (v.equals(info.getLinkDecoration())) {
-        linkDecorationBox.setSelectedIndex(i);
-      }
-      i++;
-    }
-    p.add(linkDecorationBox);
-    add(p);
-
-    if (enableImageServer) {
-      p = new HorizontalPanel();
-      p.setStyleName("imagare-label-panel");
-      p.add(new Label("Project"));
-      Image projectInfo = new Image(ImagarePlugin.RESOURCES.info());
-      projectInfo.setTitle("The default project for the image upload.");
-      p.add(projectInfo);
-      p.add(new Label(":"));
-      projectBox = new TextBox();
-      projectBox.setValue(info.getDefaultProject());
-      p.add(projectBox);
-      add(p);
-
-      p = new HorizontalPanel();
-      p.setStyleName("imagare-label-panel");
-      stageBox = new CheckBox("Stage images before upload");
-      stageBox.setValue(info.stage());
-      p.add(stageBox);
-      Image stageInfo = new Image(ImagarePlugin.RESOURCES.info());
-      stageInfo.setTitle(
-          "Images are not uploaded immediately but put into a "
-              + "staging area. The upload must be triggered explicitely.");
-      p.add(stageInfo);
-      add(p);
-    }
-
-    HorizontalPanel buttons = new HorizontalPanel();
-    add(buttons);
-
-    saveButton = new Button("Save");
-    saveButton.setStyleName("imagare-save-button");
-    saveButton.addClickHandler(
-        new ClickHandler() {
-          @Override
-          public void onClick(final ClickEvent event) {
-            doSave();
-          }
-        });
-    buttons.add(saveButton);
-    saveButton.setEnabled(false);
-    OnEditEnabler onEditEnabler = new OnEditEnabler(saveButton, linkDecorationBox);
-    if (enableImageServer) {
-      onEditEnabler.listenTo(projectBox);
-      onEditEnabler.listenTo(stageBox);
-    }
-
-    projectBox.setFocus(true);
-    saveButton.setEnabled(false);
-  }
-
-  private void doSave() {
-    ConfigInfo in = ConfigInfo.create();
-    in.setLinkDecoration(linkDecorationBox.getValue(linkDecorationBox.getSelectedIndex()));
-    if (enableImageServer) {
-      in.setDefaultProject(projectBox.getValue());
-      in.setStage(stageBox.getValue());
-    }
-    restApi.put(
-        in,
-        new AsyncCallback<JavaScriptObject>() {
-          @Override
-          public void onSuccess(JavaScriptObject result) {
-            saveButton.setEnabled(false);
-            onSave();
-          }
-
-          @Override
-          public void onFailure(Throwable caught) {
-            // never invoked
-          }
-        });
-  }
-
-  protected void onSave() {}
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/imagare/client/ImagarePlugin.java b/src/main/java/com/googlesource/gerrit/plugins/imagare/client/ImagarePlugin.java
deleted file mode 100644
index 086bd13..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/imagare/client/ImagarePlugin.java
+++ /dev/null
@@ -1,54 +0,0 @@
-// 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.googlesource.gerrit.plugins.imagare.client;
-
-import com.google.gerrit.plugin.client.Plugin;
-import com.google.gerrit.plugin.client.PluginEntryPoint;
-import com.google.gerrit.plugin.client.rpc.RestApi;
-import com.google.gwt.core.client.GWT;
-import com.google.gwt.user.client.rpc.AsyncCallback;
-
-public class ImagarePlugin extends PluginEntryPoint {
-  public static final Resources RESOURCES = GWT.create(Resources.class);
-
-  @Override
-  public void onPluginLoad() {
-    new RestApi("config")
-        .id("server")
-        .view(Plugin.get().getPluginName(), "config")
-        .get(
-            new AsyncCallback<ConfigInfo>() {
-              @Override
-              public void onSuccess(ConfigInfo info) {
-                if (info.enableImageServer()) {
-                  Plugin.get().screen("upload", new ImageUploadScreen.Factory());
-                }
-
-                Plugin.get()
-                    .screen("settings", new ImagareAdminScreen.Factory(info.enableImageServer()));
-                Plugin.get()
-                    .settingsScreen(
-                        "preferences",
-                        Plugin.get().getName() + " Preferences",
-                        new ImagarePreferenceScreen.Factory(info.enableImageServer()));
-              }
-
-              @Override
-              public void onFailure(Throwable caught) {
-                // never invoked
-              }
-            });
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/imagare/client/ImagarePreferenceScreen.java b/src/main/java/com/googlesource/gerrit/plugins/imagare/client/ImagarePreferenceScreen.java
deleted file mode 100644
index a9cd182..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/imagare/client/ImagarePreferenceScreen.java
+++ /dev/null
@@ -1,69 +0,0 @@
-// 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.googlesource.gerrit.plugins.imagare.client;
-
-import com.google.gerrit.plugin.client.Plugin;
-import com.google.gerrit.plugin.client.rpc.RestApi;
-import com.google.gerrit.plugin.client.screen.Screen;
-import com.google.gwt.user.client.Cookies;
-import com.google.gwt.user.client.ui.Anchor;
-import com.google.gwt.user.client.ui.HorizontalPanel;
-import com.google.gwt.user.client.ui.ImageResourceRenderer;
-
-public class ImagarePreferenceScreen extends ImagareConfigScreen {
-
-  static class Factory implements Screen.EntryPoint {
-    private final boolean enableImageServer;
-
-    Factory(boolean enableImageServer) {
-      this.enableImageServer = enableImageServer;
-    }
-
-    @Override
-    public void onLoad(Screen screen) {
-      screen.setPageTitle(Plugin.get().getName() + " Preferences");
-      screen.show(new ImagarePreferenceScreen(enableImageServer));
-    }
-  }
-
-  ImagarePreferenceScreen(boolean enableImageServer) {
-    super(
-        enableImageServer,
-        new RestApi("accounts").id("self").view(Plugin.get().getPluginName(), "preference"));
-  }
-
-  @Override
-  protected void display(ConfigInfo info) {
-    if (enableImageServer) {
-      HorizontalPanel p = new HorizontalPanel();
-      p.setStyleName("imagare-menu-panel");
-      Anchor uploadAnchor =
-          new Anchor(
-              new ImageResourceRenderer().render(ImagarePlugin.RESOURCES.image()),
-              "#/x/" + Plugin.get().getPluginName() + "/upload");
-      uploadAnchor.setTitle("Upload Image");
-      p.add(uploadAnchor);
-      add(p);
-    }
-
-    super.display(info);
-  }
-
-  @Override
-  protected void onSave() {
-    super.onSave();
-    Cookies.removeCookie(Plugin.get().getPluginName() + "~prefs");
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/imagare/client/ImageUploadScreen.java b/src/main/java/com/googlesource/gerrit/plugins/imagare/client/ImageUploadScreen.java
deleted file mode 100644
index a146a2e..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/imagare/client/ImageUploadScreen.java
+++ /dev/null
@@ -1,140 +0,0 @@
-// 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.googlesource.gerrit.plugins.imagare.client;
-
-import com.google.gerrit.plugin.client.Plugin;
-import com.google.gerrit.plugin.client.rpc.RestApi;
-import com.google.gerrit.plugin.client.screen.Screen;
-import com.google.gwt.http.client.URL;
-import com.google.gwt.user.client.Window;
-import com.google.gwt.user.client.rpc.AsyncCallback;
-import com.google.gwt.user.client.ui.Anchor;
-import com.google.gwt.user.client.ui.HorizontalPanel;
-import com.google.gwt.user.client.ui.Image;
-import com.google.gwt.user.client.ui.ImageResourceRenderer;
-import com.google.gwt.user.client.ui.Label;
-import com.google.gwt.user.client.ui.TextBox;
-import com.google.gwt.user.client.ui.VerticalPanel;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-public class ImageUploadScreen extends VerticalPanel {
-
-  static class Factory implements Screen.EntryPoint {
-    @Override
-    public void onLoad(Screen screen) {
-      screen.setPageTitle("Image Upload");
-      screen.show(new ImageUploadScreen());
-    }
-  }
-
-  static TextBox projectBox;
-  static UploadStagePanel uploadStagePanel;
-  static UploadedImagesPanel uploadedPanel;
-
-  private final UploadByDropOrPastePanel uploadPanel;
-
-  ImageUploadScreen() {
-    setStyleName("imagare-image-upload-screen");
-
-    HorizontalPanel p = new HorizontalPanel();
-    p.setStyleName("imagare-menu-panel");
-    Anchor prefsAnchor =
-        new Anchor(
-            new ImageResourceRenderer().render(ImagarePlugin.RESOURCES.gear()),
-            "#/settings/x/" + Plugin.get().getPluginName() + "/preferences");
-    prefsAnchor.setTitle("Edit Preferences");
-    p.add(prefsAnchor);
-    add(p);
-
-    p = new HorizontalPanel();
-    p.setStyleName("imagare-label-panel");
-    p.add(new Label("Project"));
-    Image projectInfo = new Image(ImagarePlugin.RESOURCES.info());
-    projectInfo.setTitle("The project to which the images are uploaded.");
-    p.add(projectInfo);
-    p.add(new Label(":"));
-    projectBox = new TextBox();
-    p.add(projectBox);
-    add(p);
-
-    add(new UploadByFileSelection());
-    uploadPanel = new UploadByDropOrPastePanel();
-    add(uploadPanel);
-    uploadStagePanel = new UploadStagePanel();
-    add(uploadStagePanel);
-    uploadedPanel = new UploadedImagesPanel();
-    add(uploadedPanel);
-
-    new RestApi("accounts")
-        .id("self")
-        .view(Plugin.get().getPluginName(), "preference")
-        .get(
-            new AsyncCallback<ConfigInfo>() {
-              @Override
-              public void onSuccess(ConfigInfo info) {
-                ImageUploader.setStage(info.stage());
-                String project = getParameter("project");
-                if (project != null) {
-                  projectBox.setValue(project);
-                } else {
-                  projectBox.setValue(info.getDefaultProject());
-                }
-                uploadPanel.focus();
-              }
-
-              @Override
-              public void onFailure(Throwable caught) {
-                // never invoked
-              }
-            });
-  }
-
-  private static String getParameter(String name) {
-    List<String> values = getParameters().get(name);
-    if (values == null) {
-      return null;
-    }
-    return values.get(values.size() - 1);
-  }
-
-  private static Map<String, List<String>> getParameters() {
-    Map<String, List<String>> parameter = new HashMap<>();
-
-    if (!Window.Location.getHash().contains("?")) {
-      return parameter;
-    }
-
-    String queryString =
-        Window.Location.getHash().substring(Window.Location.getHash().indexOf('?') + 1);
-    for (String kvPair : queryString.split("&")) {
-      String[] kv = kvPair.split("=", 2);
-      if (kv[0].length() == 0) {
-        continue;
-      }
-
-      List<String> values = parameter.get(kv[0]);
-      if (values == null) {
-        values = new ArrayList<>();
-        parameter.put(kv[0], values);
-      }
-      values.add(kv.length > 1 ? URL.decodeQueryString(kv[1]) : "");
-    }
-
-    return parameter;
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/imagare/client/ImageUploader.java b/src/main/java/com/googlesource/gerrit/plugins/imagare/client/ImageUploader.java
deleted file mode 100644
index 057f615..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/imagare/client/ImageUploader.java
+++ /dev/null
@@ -1,92 +0,0 @@
-// 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.googlesource.gerrit.plugins.imagare.client;
-
-import com.google.gerrit.plugin.client.Plugin;
-import com.google.gerrit.plugin.client.rpc.RestApi;
-import com.google.gwt.core.client.JavaScriptObject;
-import com.google.gwt.user.client.rpc.AsyncCallback;
-
-public class ImageUploader {
-  private static boolean stage;
-
-  public static void setStage(boolean s) {
-    stage = s;
-  }
-
-  public static final void stageImage(String imageData) {
-    stageImage(imageData, null);
-  }
-
-  public static final void stageImage(String imageData, String fileName) {
-    stageImage(ImageUploadScreen.projectBox.getValue(), imageData, fileName);
-  }
-
-  public static final void stageImage(String project, String imageData, String fileName) {
-    if (stage) {
-      ImageUploadScreen.uploadStagePanel.add(project, imageData, fileName);
-    } else {
-      uploadImage(project, imageData, fileName);
-    }
-  }
-
-  public static final void uploadImage(String imageData) {
-    uploadImage(imageData, null);
-  }
-
-  public static final void uploadImage(String imageData, String fileName) {
-    uploadImage(ImageUploadScreen.projectBox.getValue(), imageData, fileName);
-  }
-
-  public static final void uploadImage(String project, String imageData, String fileName) {
-    ImageInput in = ImageInput.create();
-    in.image_data(imageData);
-    in.file_name(fileName);
-
-    new RestApi("projects")
-        .id(project)
-        .view(Plugin.get().getPluginName(), "images")
-        .post(
-            in,
-            new AsyncCallback<ImageInfo>() {
-
-              @Override
-              public void onSuccess(ImageInfo result) {
-                ImageUploadScreen.uploadedPanel.add(result.url());
-              }
-
-              @Override
-              public void onFailure(Throwable caught) {}
-            });
-  }
-
-  private static class ImageInput extends JavaScriptObject {
-    final native void image_data(String d) /*-{ this.image_data = d; }-*/;
-
-    final native void file_name(String n) /*-{ this.file_name = n; }-*/;
-
-    static ImageInput create() {
-      return (ImageInput) createObject();
-    }
-
-    protected ImageInput() {}
-  }
-
-  private static class ImageInfo extends JavaScriptObject {
-    final native String url() /*-{ return this.url }-*/;
-
-    protected ImageInfo() {}
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/imagare/client/LinkDecoration.java b/src/main/java/com/googlesource/gerrit/plugins/imagare/client/LinkDecoration.java
deleted file mode 100644
index b44b075..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/imagare/client/LinkDecoration.java
+++ /dev/null
@@ -1,21 +0,0 @@
-// 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.googlesource.gerrit.plugins.imagare.client;
-
-public enum LinkDecoration {
-  NONE,
-  TOOLTIP,
-  INLINE
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/imagare/client/OnEditEnabler.java b/src/main/java/com/googlesource/gerrit/plugins/imagare/client/OnEditEnabler.java
deleted file mode 100644
index 7be0614..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/imagare/client/OnEditEnabler.java
+++ /dev/null
@@ -1,188 +0,0 @@
-// Copyright (C) 2010 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.googlesource.gerrit.plugins.imagare.client;
-
-import com.google.gwt.core.client.Scheduler;
-import com.google.gwt.core.client.Scheduler.ScheduledCommand;
-import com.google.gwt.event.dom.client.ChangeEvent;
-import com.google.gwt.event.dom.client.ChangeHandler;
-import com.google.gwt.event.dom.client.FocusEvent;
-import com.google.gwt.event.dom.client.FocusHandler;
-import com.google.gwt.event.dom.client.KeyDownEvent;
-import com.google.gwt.event.dom.client.KeyDownHandler;
-import com.google.gwt.event.dom.client.KeyPressEvent;
-import com.google.gwt.event.dom.client.KeyPressHandler;
-import com.google.gwt.event.dom.client.MouseUpEvent;
-import com.google.gwt.event.dom.client.MouseUpHandler;
-import com.google.gwt.event.logical.shared.ValueChangeEvent;
-import com.google.gwt.event.logical.shared.ValueChangeHandler;
-import com.google.gwt.event.shared.GwtEvent;
-import com.google.gwt.user.client.ui.CheckBox;
-import com.google.gwt.user.client.ui.FocusWidget;
-import com.google.gwt.user.client.ui.ListBox;
-import com.google.gwt.user.client.ui.TextBoxBase;
-import com.google.gwt.user.client.ui.ValueBoxBase;
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * Enables a FocusWidget (e.g. a Button) if an edit is detected from any registered input widget.
- */
-public class OnEditEnabler
-    implements KeyPressHandler,
-        KeyDownHandler,
-        MouseUpHandler,
-        ChangeHandler,
-        ValueChangeHandler<Object> {
-
-  private final FocusWidget widget;
-  private Map<TextBoxBase, String> strings = new HashMap<>();
-  private String originalValue;
-
-  // The first parameter to the constructors must be the FocusWidget to enable,
-  // subsequent parameters are widgets to listenTo.
-
-  public OnEditEnabler(final FocusWidget w, final TextBoxBase tb) {
-    this(w);
-    originalValue = tb.getValue().trim();
-    listenTo(tb);
-  }
-
-  public OnEditEnabler(final FocusWidget w, final ListBox lb) {
-    this(w);
-    listenTo(lb);
-  }
-
-  public OnEditEnabler(final FocusWidget w, final CheckBox cb) {
-    this(w);
-    listenTo(cb);
-  }
-
-  public OnEditEnabler(final FocusWidget w) {
-    widget = w;
-  }
-
-  // Register input widgets to be listened to
-
-  public void listenTo(final TextBoxBase tb) {
-    strings.put(tb, tb.getText().trim());
-    tb.addKeyPressHandler(this);
-
-    // Is there another way to capture middle button X11 pastes in browsers
-    // which do not yet support ONPASTE events (Firefox)?
-    tb.addMouseUpHandler(this);
-
-    // Resetting the "original text" on focus ensures that we are
-    // up to date with non-user updates of the text (calls to
-    // setText()...) and also up to date with user changes which
-    // occured after enabling "widget".
-    tb.addFocusHandler(
-        new FocusHandler() {
-          @Override
-          public void onFocus(FocusEvent event) {
-            strings.put(tb, tb.getText().trim());
-          }
-        });
-
-    // CTRL-V Pastes in Chrome seem only detectable via BrowserEvents or
-    // KeyDownEvents, the latter is better.
-    tb.addKeyDownHandler(this);
-  }
-
-  public void listenTo(final ListBox lb) {
-    lb.addChangeHandler(this);
-  }
-
-  @SuppressWarnings({"unchecked", "rawtypes"})
-  public void listenTo(final CheckBox cb) {
-    cb.addValueChangeHandler((ValueChangeHandler) this);
-  }
-
-  // Handlers
-
-  @Override
-  public void onKeyPress(final KeyPressEvent e) {
-    on(e);
-  }
-
-  @Override
-  public void onKeyDown(final KeyDownEvent e) {
-    on(e);
-  }
-
-  @Override
-  public void onMouseUp(final MouseUpEvent e) {
-    on(e);
-  }
-
-  @Override
-  public void onChange(final ChangeEvent e) {
-    on(e);
-  }
-
-  @SuppressWarnings("rawtypes")
-  @Override
-  public void onValueChange(final ValueChangeEvent e) {
-    on(e);
-  }
-
-  private void on(final GwtEvent<?> e) {
-    if (widget.isEnabled()
-        || !(e.getSource() instanceof FocusWidget)
-        || !((FocusWidget) e.getSource()).isEnabled()) {
-      if (e.getSource() instanceof ValueBoxBase) {
-        final TextBoxBase box = ((TextBoxBase) e.getSource());
-        Scheduler.get()
-            .scheduleDeferred(
-                new ScheduledCommand() {
-                  @Override
-                  public void execute() {
-                    if (box.getValue().trim().equals(originalValue)) {
-                      widget.setEnabled(false);
-                    }
-                  }
-                });
-      }
-      return;
-    }
-
-    if (e.getSource() instanceof TextBoxBase) {
-      onTextBoxBase((TextBoxBase) e.getSource());
-    } else {
-      // For many widgets, we can assume that a change is an edit. If
-      // a widget does not work that way, it should be special cased
-      // above.
-      widget.setEnabled(true);
-    }
-  }
-
-  private void onTextBoxBase(final TextBoxBase tb) {
-    // The text appears to not get updated until the handlers complete.
-    Scheduler.get()
-        .scheduleDeferred(
-            new ScheduledCommand() {
-              @Override
-              public void execute() {
-                String orig = strings.get(tb);
-                if (orig == null) {
-                  orig = "";
-                }
-                if (!orig.equals(tb.getText().trim())) {
-                  widget.setEnabled(true);
-                }
-              }
-            });
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/imagare/client/Resources.java b/src/main/java/com/googlesource/gerrit/plugins/imagare/client/Resources.java
deleted file mode 100644
index 7e85e6e..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/imagare/client/Resources.java
+++ /dev/null
@@ -1,29 +0,0 @@
-// 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.googlesource.gerrit.plugins.imagare.client;
-
-import com.google.gwt.resources.client.ImageResource;
-
-public interface Resources extends com.google.gerrit.client.Resources {
-
-  @Source("delete.png")
-  public ImageResource delete();
-
-  @Source("fullscreen.png")
-  public ImageResource fullScreen();
-
-  @Source("image.png")
-  public ImageResource image();
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/imagare/client/UploadByDropOrPastePanel.java b/src/main/java/com/googlesource/gerrit/plugins/imagare/client/UploadByDropOrPastePanel.java
deleted file mode 100644
index c4ed0d5..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/imagare/client/UploadByDropOrPastePanel.java
+++ /dev/null
@@ -1,161 +0,0 @@
-// 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.googlesource.gerrit.plugins.imagare.client;
-
-import com.google.gwt.dom.client.Element;
-import com.google.gwt.user.client.ui.Label;
-import com.google.gwt.user.client.ui.VerticalPanel;
-
-public class UploadByDropOrPastePanel extends VerticalPanel {
-
-  UploadByDropOrPastePanel() {
-    init0();
-
-    setStyleName("imagare-image-upload-panel");
-    getElement().setAttribute("contenteditable", "true");
-    getElement().setAttribute("onpaste", "imagarePasteHandler(this, event)");
-    getElement().setAttribute("ondrop", "imagareDropHandler(this, event)");
-    getElement().setAttribute("onkeypress", "imagarePreventKeyPress(event)");
-
-    add(new Label("drag and drop image here"));
-    add(new Label("or"));
-    add(new Label("paste image from clipboard with " + (isMac() ? "Cmd+V" : "Ctrl+V")));
-  }
-
-  private static native boolean isMac() /*-{
-    return navigator.platform.toUpperCase().indexOf('MAC') >= 0;
-  }-*/;
-
-  private static native void init0() /*-{
-    $wnd.imagarePreventKeyPress = function preventKeys(event) {
-      event = event || window.event;
-      var ctrlDown = event.ctrlKey || event.metaKey;
-      if (!ctrlDown) {
-        event.preventDefault();
-      }
-    }
-
-    var imagareSavedContent;
-
-    $wnd.imagarePasteHandler = function handlePaste(elem, e) {
-      if (!imagareSavedContent) {
-        imagareSavedContent = elem.innerHTML;
-      }
-
-      var clipboardData = e.clipboardData || e.originalEvent.clipboardData;
-      var items = clipboardData.items;
-
-      if (JSON.stringify(items)) {
-        // Chrome
-
-        var blob;
-        for (var i = 0; i < items.length; i++) {
-          if (items[i].type.indexOf("image") === 0) {
-            blob = items[i].getAsFile();
-          }
-        }
-
-        if (blob) {
-          var reader = new FileReader();
-          reader.onload = function(e) {
-            @com.googlesource.gerrit.plugins.imagare.client.ImageUploader::stageImage(Ljava/lang/String;)(e.target.result);
-          };
-          reader.readAsDataURL(blob);
-        } else {
-          e.preventDefault();
-          $wnd.Gerrit.showError('no image data');
-        }
-      } else if (e && e.clipboardData && e.clipboardData.getData) {
-        // Webkit
-
-        if ((/text\/html/.test(e.clipboardData.types[0]))
-          || (/text\/plain/.test(e.clipboardData.types[0]))) {
-          elem.innerHTML = '<div>' + e.clipboardData.getData(e.clipboardData.types[0]) + '</div>';
-        } else {
-          elem.innerHTML = "";
-        }
-
-        waitOnPaste(10, elem);
-      } else {
-        // other browser
-
-        elem.innerHTML = "";
-        waitOnPaste(10, elem);
-      }
-    }
-
-    function waitOnPaste(max, elem) {
-      if (elem.childNodes && elem.childNodes.length > 0) {
-        stageImage(elem);
-      } else if (max > 0) {
-        that = {
-          m: max - 1,
-          e: elem,
-        }
-        that.callself = function () {
-          waitOnPaste(that.m, that.e)
-        }
-        setTimeout(that.callself, 20);
-      }
-    }
-
-    function stageImage(elem) {
-      var imageData = elem.childNodes[0].getAttribute("src");
-      elem.innerHTML = imagareSavedContent;
-      @com.googlesource.gerrit.plugins.imagare.client.ImageUploader::stageImage(Ljava/lang/String;)(imageData);
-    }
-
-    $wnd.imagareDropHandler = function handleDrop(elem, event) {
-      if (window.chrome) {
-        event.preventDefault();
-      }
-      if (!imagareSavedContent) {
-        imagareSavedContent = elem.innerHTML;
-      }
-      for(var i = 0; i < event.dataTransfer.files.length; i++) {
-        var f = event.dataTransfer.files[i];
-        if (f) {
-          if (!f.type.match('image/.*')) {
-            $wnd.Gerrit.showError('no image file: ' + f.name);
-          }
-
-          var r = new FileReader();
-          r.file = f;
-          r.onload = function(e) {
-            elem.innerHTML = imagareSavedContent;
-            @com.googlesource.gerrit.plugins.imagare.client.ImageUploader::stageImage(Ljava/lang/String;Ljava/lang/String;)(e.target.result, this.file.name);
-          }
-          r.readAsDataURL(f);
-        } else {
-          $wnd.Gerrit.showError('Failed to load file: ' + f.name);
-        }
-      }
-    }
-  }-*/;
-
-  public void focus() {
-    focus(getElement());
-  }
-
-  private static native void focus(Element elem) /*-{
-    var range = document.createRange();
-    var sel = window.getSelection();
-    range.selectNode(elem);
-    range.collapse(true);
-    sel.removeAllRanges();
-    sel.addRange(range);
-    elem.focus();
-  }-*/;
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/imagare/client/UploadByFileSelection.java b/src/main/java/com/googlesource/gerrit/plugins/imagare/client/UploadByFileSelection.java
deleted file mode 100644
index 516644b..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/imagare/client/UploadByFileSelection.java
+++ /dev/null
@@ -1,56 +0,0 @@
-// 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.googlesource.gerrit.plugins.imagare.client;
-
-import com.google.gwt.user.client.ui.FileUpload;
-import com.google.gwt.user.client.ui.FormPanel;
-import com.google.gwt.user.client.ui.HorizontalPanel;
-
-public class UploadByFileSelection extends HorizontalPanel {
-  UploadByFileSelection() {
-    init0();
-
-    FormPanel form = new FormPanel();
-    FileUpload upload = new FileUpload();
-    upload.setName("Select Image");
-    upload.getElement().setAttribute("multiple", "multiple");
-    upload.getElement().setAttribute("onChange", "imagareSubmit(event)");
-    form.add(upload);
-
-    add(form);
-  }
-
-  private static native void init0() /*-{
-    $wnd.imagareSubmit = function submit(event) {
-      for(var i = 0; i < event.target.files.length; i++) {
-        var f = event.target.files[i];
-        if (f) {
-          if (!f.type.match('image/.*')) {
-            $wnd.Gerrit.showError('no image file: ' + f.name);
-          }
-
-          var r = new FileReader();
-          r.file = f;
-          r.onload = function(e) {
-            @com.googlesource.gerrit.plugins.imagare.client.ImageUploader::stageImage(Ljava/lang/String;Ljava/lang/String;)(e.target.result, this.file.name);
-          }
-          r.readAsDataURL(f);
-        } else {
-          $wnd.Gerrit.showError('Failed to load file: ' + f.name);
-        }
-      }
-    }
-  }-*/;
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/imagare/client/UploadStagePanel.java b/src/main/java/com/googlesource/gerrit/plugins/imagare/client/UploadStagePanel.java
deleted file mode 100644
index 4ff11b0..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/imagare/client/UploadStagePanel.java
+++ /dev/null
@@ -1,328 +0,0 @@
-// 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.googlesource.gerrit.plugins.imagare.client;
-
-import static com.google.gwt.event.dom.client.KeyCodes.KEY_ENTER;
-import static com.google.gwt.event.dom.client.KeyCodes.KEY_ESCAPE;
-
-import com.google.gwt.event.dom.client.BlurEvent;
-import com.google.gwt.event.dom.client.BlurHandler;
-import com.google.gwt.event.dom.client.ClickEvent;
-import com.google.gwt.event.dom.client.ClickHandler;
-import com.google.gwt.event.dom.client.DoubleClickEvent;
-import com.google.gwt.event.dom.client.DoubleClickHandler;
-import com.google.gwt.event.dom.client.KeyDownEvent;
-import com.google.gwt.event.dom.client.KeyDownHandler;
-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.Button;
-import com.google.gwt.user.client.ui.FlowPanel;
-import com.google.gwt.user.client.ui.HorizontalPanel;
-import com.google.gwt.user.client.ui.Image;
-import com.google.gwt.user.client.ui.Label;
-import com.google.gwt.user.client.ui.PopupPanel;
-import com.google.gwt.user.client.ui.TextBox;
-import com.google.gwt.user.client.ui.VerticalPanel;
-
-public class UploadStagePanel extends VerticalPanel {
-
-  private Label uploadedImagesLabel;
-  private FlowPanel stagedImagesPanel;
-  private PopupPanel popup;
-
-  UploadStagePanel() {
-    setStyleName("imagare-upload-stage-panel");
-    setVisible(false);
-
-    uploadedImagesLabel = new Label("Staged Images:");
-    uploadedImagesLabel.setStyleName("imagare-staged-images-label");
-    add(uploadedImagesLabel);
-
-    stagedImagesPanel = new FlowPanel();
-    stagedImagesPanel.setStyleName("imagare-staged-images-panel");
-    add(stagedImagesPanel);
-
-    HorizontalPanel buttons = new HorizontalPanel();
-    add(buttons);
-
-    Button uploadButton = new Button("Upload");
-    uploadButton.setStyleName("imagare-upload-button");
-    uploadButton.addClickHandler(
-        new ClickHandler() {
-          @Override
-          public void onClick(final ClickEvent event) {
-            doUpload();
-          }
-        });
-    buttons.add(uploadButton);
-
-    Button cleanButton = new Button("Clean");
-    cleanButton.setStyleName("imagare-clean-button");
-    cleanButton.addClickHandler(
-        new ClickHandler() {
-          @Override
-          public void onClick(final ClickEvent event) {
-            stagedImagesPanel.clear();
-            setVisible(false);
-          }
-        });
-    buttons.add(cleanButton);
-
-    popup = new PopupPanel();
-    popup.setVisible(false);
-  }
-
-  void add(String project, String dataUrl, String fileName) {
-    setVisible(true);
-    if (!isStaged(project, dataUrl, fileName)) {
-      stagedImagesPanel.insert(new ImagePreview(project, dataUrl, fileName), 0);
-    }
-  }
-
-  boolean isStaged(String project, String dataUrl, String fileName) {
-    for (int i = 0; i < stagedImagesPanel.getWidgetCount(); i++) {
-      ImagePreview ip = (ImagePreview) stagedImagesPanel.getWidget(i);
-      if (project.equals(ip.project)
-          && dataUrl.endsWith(ip.dataUrl)
-          && (fileName != null ? fileName.equals(ip.fileName) : ip.fileName == null)) {
-        return true;
-      }
-    }
-    return false;
-  }
-
-  private void doUpload() {
-    for (int i = 0; i < stagedImagesPanel.getWidgetCount(); i++) {
-      ImagePreview ip = (ImagePreview) stagedImagesPanel.getWidget(i);
-      ImageUploader.uploadImage(ip.project, ip.dataUrl, ip.fileName);
-    }
-    stagedImagesPanel.clear();
-    setVisible(false);
-  }
-
-  private class ImagePreview extends VerticalPanel {
-    final String project;
-    final String dataUrl;
-    String fileName;
-
-    private final Image img;
-    private final Image deleteIcon;
-    private Timer deleteIconHideTimer;
-
-    ImagePreview(String project, final String dataUrl, String fileName) {
-      this.project = project;
-      this.dataUrl = dataUrl;
-      this.fileName = fileName;
-
-      setStyleName("imagare-stage-image-preview-panel");
-
-      addFileName();
-
-      img = new Image(dataUrl);
-      img.setStyleName("imagare-stage-image-preview");
-      add(img);
-
-      deleteIcon = new Image(ImagarePlugin.RESOURCES.delete());
-      deleteIcon.setStyleName("imagare-delete-icon");
-      deleteIcon.setTitle("Delete Image");
-      deleteIcon.setVisible(false);
-      add(deleteIcon);
-
-      img.addMouseOverHandler(
-          new MouseOverHandler() {
-            @Override
-            public void onMouseOver(MouseOverEvent event) {
-              if (!popup.isVisible()) {
-                Image previewImg = new Image(dataUrl);
-                previewImg.setStyleName("imagare-image-popup");
-                previewImg
-                    .getElement()
-                    .setAttribute(
-                        "style",
-                        previewImg.getElement().getAttribute("style")
-                            + "position: absolute; top: "
-                            + (img.getAbsoluteTop() + img.getHeight() + 20)
-                            + "px; "
-                            + "left: "
-                            + img.getAbsoluteLeft()
-                            + "px;");
-                popup.add(previewImg);
-
-                popup.show();
-                popup.setVisible(true);
-              }
-
-              cancelHideDeleteIcon();
-              deleteIcon
-                  .getElement()
-                  .setAttribute(
-                      "style",
-                      deleteIcon.getElement().getAttribute("style")
-                          + "position: absolute; top: "
-                          + img.getAbsoluteTop()
-                          + "px; "
-                          + "left: "
-                          + img.getAbsoluteLeft()
-                          + "px;");
-              deleteIcon.setVisible(true);
-            }
-          });
-      img.addMouseOutHandler(
-          new MouseOutHandler() {
-            @Override
-            public void onMouseOut(MouseOutEvent event) {
-              popup.setVisible(false);
-              popup.clear();
-              hideDeleteIcon();
-            }
-          });
-
-      deleteIcon.addMouseOverHandler(
-          new MouseOverHandler() {
-            @Override
-            public void onMouseOver(MouseOverEvent event) {
-              cancelHideDeleteIcon();
-            }
-          });
-
-      deleteIcon.addMouseOutHandler(
-          new MouseOutHandler() {
-            @Override
-            public void onMouseOut(MouseOutEvent event) {
-              hideDeleteIcon();
-            }
-          });
-
-      deleteIcon.addClickHandler(
-          new ClickHandler() {
-            @Override
-            public void onClick(ClickEvent event) {
-              stagedImagesPanel.remove(ImagePreview.this);
-              UploadStagePanel.this.setVisible(stagedImagesPanel.getWidgetCount() != 0);
-              popup.setVisible(false);
-              popup.clear();
-            }
-          });
-
-      Label projectLabel = new Label("Project: " + project);
-      projectLabel.setStyleName("imagare-stage-label");
-      add(projectLabel);
-    }
-
-    private void hideDeleteIcon() {
-      deleteIconHideTimer =
-          new Timer() {
-            @Override
-            public void run() {
-              deleteIcon.setVisible(false);
-            }
-          };
-      deleteIconHideTimer.schedule(20);
-    }
-
-    private void cancelHideDeleteIcon() {
-      if (deleteIconHideTimer != null) {
-        deleteIconHideTimer.cancel();
-        deleteIconHideTimer = null;
-      }
-    }
-
-    private void addFileName() {
-      final Label fileNameLabel = new Label(fileName != null ? fileName : "img.png");
-      fileNameLabel.setStyleName("imagare-stage-image-title");
-      add(fileNameLabel);
-
-      fileNameLabel.addDoubleClickHandler(
-          new DoubleClickHandler() {
-            @Override
-            public void onDoubleClick(DoubleClickEvent event) {
-              fileNameLabel.setVisible(false);
-              FileNameEditPanel fileNameEditPanel =
-                  new FileNameEditPanel(ImagePreview.this, fileNameLabel);
-              insert(fileNameEditPanel, getWidgetIndex(fileNameLabel));
-              fileNameEditPanel.focusAndSelectAll();
-            }
-          });
-    }
-
-    private class FileNameEditPanel extends HorizontalPanel {
-      private final ImagePreview imagePreview;
-      private final Label fileNameLabel;
-      private final TextBox fileNameBox;
-      private final String name;
-      private final String extension;
-
-      FileNameEditPanel(ImagePreview imagePreview, Label fileNameLabel) {
-        this.imagePreview = imagePreview;
-        this.fileNameLabel = fileNameLabel;
-        int pos = imagePreview.fileName.lastIndexOf('.');
-        if (pos != -1) {
-          this.name = imagePreview.fileName.substring(0, pos);
-          this.extension = imagePreview.fileName.substring(pos);
-        } else {
-          this.name = imagePreview.fileName;
-          this.extension = "";
-        }
-
-        setStyleName("imagare-stage-edit-panel");
-        fileNameBox = new TextBox();
-        fileNameBox.setStyleName("imagare-stage-input");
-        fileNameBox.setValue(name);
-        add(fileNameBox);
-        add(new Label(extension));
-
-        fileNameBox.addBlurHandler(
-            new BlurHandler() {
-              @Override
-              public void onBlur(BlurEvent event) {
-                saveChanges();
-              }
-            });
-
-        fileNameBox.addKeyDownHandler(
-            new KeyDownHandler() {
-              @Override
-              public void onKeyDown(KeyDownEvent event) {
-                if (event.getNativeKeyCode() == KEY_ESCAPE) {
-                  discardChanges();
-                } else if (event.getNativeKeyCode() == KEY_ENTER) {
-                  saveChanges();
-                }
-              }
-            });
-      }
-
-      private void saveChanges() {
-        imagePreview.fileName = fileNameBox.getValue() + extension;
-        fileNameLabel.setText(fileNameBox.getValue() + extension);
-        fileNameLabel.setVisible(true);
-        removeFromParent();
-      }
-
-      private void discardChanges() {
-        fileNameLabel.setVisible(true);
-        removeFromParent();
-      }
-
-      void focusAndSelectAll() {
-        fileNameBox.setFocus(true);
-        fileNameBox.selectAll();
-      }
-    }
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/imagare/client/UploadedImagesPanel.java b/src/main/java/com/googlesource/gerrit/plugins/imagare/client/UploadedImagesPanel.java
deleted file mode 100644
index 2e010c3..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/imagare/client/UploadedImagesPanel.java
+++ /dev/null
@@ -1,286 +0,0 @@
-// 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.googlesource.gerrit.plugins.imagare.client;
-
-import com.google.gerrit.plugin.client.Plugin;
-import com.google.gerrit.plugin.client.rpc.NoContent;
-import com.google.gerrit.plugin.client.rpc.RestApi;
-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.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.FlowPanel;
-import com.google.gwt.user.client.ui.Image;
-import com.google.gwt.user.client.ui.Label;
-import com.google.gwt.user.client.ui.PopupPanel;
-import com.google.gwt.user.client.ui.VerticalPanel;
-import com.google.gwtexpui.clippy.client.CopyableLabel;
-import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
-
-public class UploadedImagesPanel extends FlowPanel {
-
-  private Label uploadedImagesLabel;
-  private PopupPanel popup;
-
-  UploadedImagesPanel() {
-    setStyleName("imagare-uploaded-images-panel");
-
-    uploadedImagesLabel = new Label("Uploaded Images:");
-    uploadedImagesLabel.setStyleName("imagare-uploaded-images-label");
-    uploadedImagesLabel.setVisible(false);
-    add(uploadedImagesLabel);
-
-    popup = new PopupPanel();
-    popup.setVisible(false);
-  }
-
-  void add(final String url) {
-    uploadedImagesLabel.setVisible(true);
-    insert(new ImagePreview(url), 1);
-  }
-
-  private class ImagePreview extends VerticalPanel {
-    private final Image img;
-    private final Image fullScreenIcon;
-    private Timer fullScreenIconHideTimer;
-    private final Image deleteIcon;
-    private Timer deleteIconHideTimer;
-
-    ImagePreview(final String url) {
-      setStyleName("imagare-uploaded-image-preview-panel");
-
-      int revIndex = url.indexOf("/rev/");
-      int projectIndex = url.lastIndexOf('/', revIndex - 1);
-      int fileIndex = url.indexOf('/', revIndex + 5);
-      final String project = url.substring(projectIndex + 1, revIndex);
-      final String ref = url.substring(revIndex + 5, fileIndex);
-      final String fileName = url.substring(fileIndex + 1).replaceAll("\\+", " ");
-      Label fileNameLabel = new Label(fileName);
-      fileNameLabel.setStyleName("imagare-uploaded-image-title");
-      add(fileNameLabel);
-
-      img = new Image(url);
-      img.setStyleName("imagare-uploaded-image-preview");
-      add(img);
-
-      fullScreenIcon = new Image(ImagarePlugin.RESOURCES.fullScreen());
-      fullScreenIcon.setStyleName("imagare-fullscreen-icon");
-      fullScreenIcon.setTitle("Full Screen");
-      fullScreenIcon.setVisible(false);
-      add(fullScreenIcon);
-
-      deleteIcon = new Image(ImagarePlugin.RESOURCES.delete());
-      deleteIcon.setStyleName("imagare-delete-icon");
-      deleteIcon.setTitle("Delete Image");
-      deleteIcon.setVisible(false);
-      add(deleteIcon);
-
-      img.addMouseOverHandler(
-          new MouseOverHandler() {
-            @Override
-            public void onMouseOver(MouseOverEvent event) {
-              if (!popup.isVisible()) {
-                Image previewImg = new Image(url);
-                previewImg.setStyleName("imagare-image-popup");
-                previewImg
-                    .getElement()
-                    .setAttribute(
-                        "style",
-                        previewImg.getElement().getAttribute("style")
-                            + "position: absolute; top: "
-                            + (img.getAbsoluteTop() + img.getHeight() + 20)
-                            + "px; "
-                            + "left: "
-                            + img.getAbsoluteLeft()
-                            + "px;");
-                popup.add(previewImg);
-
-                popup.show();
-                popup.setVisible(true);
-              }
-
-              cancelHideFullScreenIcon();
-              fullScreenIcon
-                  .getElement()
-                  .setAttribute(
-                      "style",
-                      fullScreenIcon.getElement().getAttribute("style")
-                          + "position: absolute; top: "
-                          + img.getAbsoluteTop()
-                          + "px; "
-                          + "left: "
-                          + img.getAbsoluteLeft()
-                          + "px;");
-              fullScreenIcon.setVisible(true);
-
-              cancelHideDeleteIcon();
-              deleteIcon
-                  .getElement()
-                  .setAttribute(
-                      "style",
-                      deleteIcon.getElement().getAttribute("style")
-                          + "position: absolute; top: "
-                          + img.getAbsoluteTop()
-                          + "px; "
-                          + "left: "
-                          + (img.getAbsoluteLeft() + fullScreenIcon.getWidth())
-                          + "px;");
-              deleteIcon.setVisible(true);
-            }
-          });
-      img.addMouseOutHandler(
-          new MouseOutHandler() {
-            @Override
-            public void onMouseOut(MouseOutEvent event) {
-              popup.setVisible(false);
-              popup.clear();
-              hideFullScreenIcon();
-              hideDeleteIcon();
-            }
-          });
-
-      fullScreenIcon.addMouseOverHandler(
-          new MouseOverHandler() {
-            @Override
-            public void onMouseOver(MouseOverEvent event) {
-              cancelHideFullScreenIcon();
-              cancelHideDeleteIcon();
-            }
-          });
-
-      fullScreenIcon.addMouseOutHandler(
-          new MouseOutHandler() {
-            @Override
-            public void onMouseOut(MouseOutEvent event) {
-              hideFullScreenIcon();
-              hideDeleteIcon();
-            }
-          });
-
-      fullScreenIcon.addClickHandler(
-          new ClickHandler() {
-            @Override
-            public void onClick(ClickEvent event) {
-              Window.open(url, "_blank", "");
-              popup.setVisible(false);
-              popup.clear();
-            }
-          });
-
-      deleteIcon.addMouseOverHandler(
-          new MouseOverHandler() {
-            @Override
-            public void onMouseOver(MouseOverEvent event) {
-              cancelHideFullScreenIcon();
-              cancelHideDeleteIcon();
-            }
-          });
-
-      deleteIcon.addMouseOutHandler(
-          new MouseOutHandler() {
-            @Override
-            public void onMouseOut(MouseOutEvent event) {
-              hideDeleteIcon();
-              hideFullScreenIcon();
-            }
-          });
-
-      deleteIcon.addClickHandler(
-          new ClickHandler() {
-            @Override
-            public void onClick(ClickEvent event) {
-              new ConfirmationDialog(
-                      "Delete Image",
-                      new SafeHtmlBuilder()
-                          .append("Are you sure you want to delete '" + fileName + "'?")
-                          .br()
-                          .br(),
-                      new ConfirmationCallback() {
-                        @Override
-                        public void onOk() {
-                          new RestApi("projects")
-                              .view(project)
-                              .view(Plugin.get().getPluginName(), "images")
-                              .view(ref)
-                              .delete(
-                                  new AsyncCallback<NoContent>() {
-                                    @Override
-                                    public void onSuccess(NoContent info) {
-                                      UploadedImagesPanel.this.remove(ImagePreview.this);
-                                      uploadedImagesLabel.setVisible(
-                                          UploadedImagesPanel.this.getWidgetCount() > 1);
-
-                                      popup.setVisible(false);
-                                      popup.clear();
-                                    }
-
-                                    @Override
-                                    public void onFailure(Throwable caught) {
-                                      // never invoked
-                                    }
-                                  });
-                        }
-                      })
-                  .center();
-            }
-          });
-
-      CopyableLabel copyLabel = new CopyableLabel(url);
-      copyLabel.setStyleName("imagare-uploaded-copy-label");
-      add(copyLabel);
-    }
-
-    private void hideFullScreenIcon() {
-      fullScreenIconHideTimer =
-          new Timer() {
-            @Override
-            public void run() {
-              fullScreenIcon.setVisible(false);
-            }
-          };
-      fullScreenIconHideTimer.schedule(20);
-    }
-
-    private void cancelHideFullScreenIcon() {
-      if (fullScreenIconHideTimer != null) {
-        fullScreenIconHideTimer.cancel();
-        fullScreenIconHideTimer = null;
-      }
-    }
-
-    private void hideDeleteIcon() {
-      deleteIconHideTimer =
-          new Timer() {
-            @Override
-            public void run() {
-              deleteIcon.setVisible(false);
-            }
-          };
-      deleteIconHideTimer.schedule(20);
-    }
-
-    private void cancelHideDeleteIcon() {
-      if (deleteIconHideTimer != null) {
-        deleteIconHideTimer.cancel();
-        deleteIconHideTimer = null;
-      }
-    }
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/imagare/client/delete.png b/src/main/java/com/googlesource/gerrit/plugins/imagare/client/delete.png
deleted file mode 100644
index ea03150..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/imagare/client/delete.png
+++ /dev/null
Binary files differ
diff --git a/src/main/java/com/googlesource/gerrit/plugins/imagare/client/fullscreen.png b/src/main/java/com/googlesource/gerrit/plugins/imagare/client/fullscreen.png
deleted file mode 100644
index ffdabd4..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/imagare/client/fullscreen.png
+++ /dev/null
Binary files differ
diff --git a/src/main/java/com/googlesource/gerrit/plugins/imagare/client/image.png b/src/main/java/com/googlesource/gerrit/plugins/imagare/client/image.png
deleted file mode 100644
index 68da502..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/imagare/client/image.png
+++ /dev/null
Binary files differ
diff --git a/src/main/java/com/googlesource/gerrit/plugins/imagare/public/imagare.css b/src/main/java/com/googlesource/gerrit/plugins/imagare/public/imagare.css
deleted file mode 100644
index 5d0aa57..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/imagare/public/imagare.css
+++ /dev/null
@@ -1,150 +0,0 @@
-.imagare-image-upload-screen, .imagare-uploaded-images-panel,
-imagare-upload-stage-panel, .imagare-config-screen {
-  width: 100%;
-  border-spacing: 0px 5px;
-}
-
-.imagare-uploaded-images-panel {
-  width: 1000px;
-}
-
-.imagare-label-panel {
-  margin-bottom: 10px;
-}
-
-.imagare-label-panel td {
-  vertical-align: middle !important;
-}
-
-.imagare-label-panel td div {
-  margin-right: 5px;
-}
-
-.imagare-uploaded-images-panel td {
-  vertical-align: middle !important;
-}
-
-.imagare-menu-panel {
-  width: 350px;
-  background-color: #F6F6F6;
-  margin-bottom: 15px;
-}
-
-.imagare-menu-panel img {
-  float: right;
-  margin-top: 1px;
-  margin-left: 3px;
-  margin-right: 1px;
-}
-
-.imagare-image-upload-panel {
-  width: 350px;
-  border: 1px solid #B3B2B2;
-  border-spacing: 5px 5px;
-  background-color: #EEE;
-}
-
-.imagare-image-upload-panel td {
-  text-align: center;
-  font-size: 18px;
-  margin-bottom: 3px;
-}
-
-.imagare-image-upload-panel div {
-  color: #999;
-}
-
-.imagare-uploaded-images-label,
-.imagare-staged-images-label {
-  font-size: 20px;
-}
-
-.imagare-image-preview {
-  max-width: 150px;
-  max-height: 100px;
-  margin: 5px;
-  border: 1px solid #B3B2B2;
-}
-
-.imagare-stage-image-title,
-.imagare-uploaded-image-title {
-  margin-top: 10px;
-  max-width: 150px;
-  font-size: 12px;
-  font-weight: bold;
-}
-
-.imagare-uploaded-image-title {
-  max-width: 150px;
-}
-
-.imagare-stage-edit-panel td {
-  vertical-align: middle !important;
-}
-
-.imagare-stage-input {
-  margin-top: 10px;
-  font-size: 12px;
-}
-
-.imagare-stage-image-preview,
-.imagare-uploaded-image-preview {
-  max-width: 150px;
-  max-height: 100px;
-  border: 1px solid #B3B2B2;
-}
-
-.imagare-uploaded-copy-label span {
-  max-width: 150px;
-  width: 500px;
-  white-space: nowrap;
-  display: inline-block;
-  overflow: hidden;
-  text-overflow: ellipsis;
-}
-
-.imagare-image-popup {
-  border: 1px solid #B3B2B2;
-  max-width: 500px;
-  max-height: 500px;
-}
-
-.imagare-stage-image-preview-panel,
-.imagare-uploaded-image-preview-panel {
-  display: inline-block;
-  margin-left: 10px;
-  margin-right: 10px;
-}
-
-.imagare-stage-image-preview-panel td,
-.imagare-uploaded-image-preview-panel td {
-  text-align: center;
-}
-
-.imagare-staged-images-panel {
-  width: 1000px;
-}
-
-.imagare-stage-label {
-  font-size: 12px;
-  max-width: 150px;
-}
-
-.imagare-save-button {
-  margin-bottom: 10px;
-}
-
-.imagare-upload-button, .imagare-clean-button {
-  margin-right: 10px;
-  margin-top: 10px;
-  margin-bottom: 10px;
-}
-
-.imagare-delete-icon,
-.imagare-fullscreen-icon {
-  z-index: 100;
-  width: 16px;
-  height: 16px;
-  background-color: #FFF !important;
-  border: 1px solid #B3B2B2;
-}
diff --git a/src/main/resources/static/gr-imagare-inline.html b/src/main/resources/static/gr-imagare-inline.html
new file mode 100644
index 0000000..9b698fa
--- /dev/null
+++ b/src/main/resources/static/gr-imagare-inline.html
@@ -0,0 +1,19 @@
+ <!--
+@license
+Copyright (C) 2019 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.
+-->
+
+<dom-module id="gr-imagare-inline">
+  <template>
+  </template>
+  <script src="gr-imagare-inline.js"></script>
+</dom-module>
diff --git a/src/main/resources/static/gr-imagare-inline.js b/src/main/resources/static/gr-imagare-inline.js
new file mode 100644
index 0000000..3001252
--- /dev/null
+++ b/src/main/resources/static/gr-imagare-inline.js
@@ -0,0 +1,189 @@
+// Copyright (C) 2019 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.
+
+(function () {
+  'use strict';
+
+  const LINK_DECORATIONS = {
+    NONE: 1,
+    INLINE: 2,
+    TOOLTIP: 3,
+  };
+
+  Polymer({
+    is: 'gr-imagare-inline',
+
+    properties: {
+      _expandedObserver: MutationObserver,
+      _messageAddedObserver: MutationObserver,
+      _messages: Object,
+      _link_decoration: Number,
+      _pattern: String,
+      _decorator_fn: Function,
+    },
+
+    attached() {
+      this._getAccountPrefs().then(() => {
+        if (this._link_decoration === LINK_DECORATIONS.NONE) {
+          return;
+        }
+
+        this._expandedObserver = new MutationObserver(mutations => {
+          mutations.forEach(mut => {
+            if (!mut.target.classList.contains('expanded')){
+              return;
+            }
+            let links = this._getLinksFromMessage(mut.target);
+
+            if (!links) {
+              return;
+            }
+
+            for (const link of links) {
+              this._decorator_fn(link);
+            }
+          });
+        });
+
+        this._messageAddedObserver = new MutationObserver(mutations => {
+          mutations.forEach(mut => {
+            mut.addedNodes.forEach(node => {
+              if (node.tagName === "GR-MESSAGE") {
+                this._addExpandedObservers(node);
+              }
+            });
+          });
+        });
+
+        this._messageAddedObserver.observe(
+          document.getElementsByTagName('gr-messages-list')[0],
+          {
+            childList: true,
+          });
+
+        this._addObserversToMessages();
+      });
+    },
+
+    detached() {
+      this._expandedObserver.disconnect();
+      this._messageAddedObserver.disconnect();
+    },
+
+    _addObserversToMessages() {
+      this._messages = this._getMessages();
+
+      if (!this._messages) {
+        return;
+      }
+
+      for (const message of this._messages) {
+        this._addExpandedObservers(message);
+      }
+    },
+
+    _addExpandedObservers(message) {
+      this._expandedObserver.observe(message, {
+        attributes: true,
+        attributeOldValue: true,
+        attributFilter: ['class'],
+      });
+    },
+
+    _getAccountPrefs() {
+      return this.plugin.restApi('/accounts/self/imagare~preference')
+        .get('')
+        .then(prefs => {
+          if (!prefs || !prefs.link_decoration) {
+            this._link_decoration = LINK_DECORATIONS.NONE;
+            this._pattern = '.*';
+          } else {
+            this._link_decoration = LINK_DECORATIONS[prefs.link_decoration.toUpperCase()];
+            this._pattern = prefs.pattern || '.*';
+          }
+
+          switch (this._link_decoration) {
+            case LINK_DECORATIONS.INLINE:
+              this._decorator_fn = this._insertImage.bind(this);
+              break;
+            case LINK_DECORATIONS.TOOLTIP:
+              this._decorator_fn = this._addTooltip.bind(this);
+              break;
+            case LINK_DECORATIONS.NONE:
+            default:
+              this._decorator_fn = () => {};
+          }
+        });
+    },
+
+    _getMessages() {
+      let messageList = document.getElementsByTagName('gr-messages-list')[0];
+      if (messageList) {
+        return messageList.getElementsByTagName('gr-message');
+      }
+    },
+
+    _getLinksFromMessage(message) {
+      let links = [];
+      let linkedTexts = message.getElementsByTagName('gr-linked-text');
+      for (const e of linkedTexts) {
+        let aTags = e.getElementsByTagName('a');
+        if (aTags && aTags.length > 0){
+          for (const a of aTags){
+            if (a.getElementsByTagName('img').length > 0) {
+              continue;
+            }
+            if (!a.href.match(this._pattern)) {
+              continue;
+            }
+
+            links = links.concat(a);
+          }
+        }
+      }
+      return links.length > 0 ? links : null;
+    },
+
+    _createImage(url) {
+      let img = document.createElement('img');
+      img.setAttribute("src", url);
+      img.setAttribute("style", "max-width: 100%; height: auto;");
+
+      return img;
+    },
+
+    _insertImage(link) {
+      if (!link) {
+        return;
+      }
+
+      link.replaceWith(this._createImage(link.href));
+    },
+
+    _addTooltip(link) {
+      if (!link) {
+        return;
+      }
+
+      link.onmouseover = (event) => {
+        let img = this._createImage(link.href);
+        img.onmouseout = (event) => {
+          event.target.replaceWith(link);
+        }
+
+        event.target.replaceWith(img);
+      }
+    },
+  });
+})();
diff --git a/src/main/resources/static/gr-imagare-list-item.html b/src/main/resources/static/gr-imagare-list-item.html
new file mode 100644
index 0000000..2f66329
--- /dev/null
+++ b/src/main/resources/static/gr-imagare-list-item.html
@@ -0,0 +1,135 @@
+<!--
+@license
+Copyright (C) 2019 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.
+-->
+
+<dom-module id="gr-imagare-list-item">
+  <template>
+    <style include="shared-styles"></style>
+    <style include="gr-subpage-styles"></style>
+    <style>
+      div.image-panel {
+        margin: 2em auto;
+        max-width: 50em;
+        height: 150px;
+      }
+
+      div.title {
+        float: left;
+        width: 30%;
+        height: 100%;
+      }
+
+      div.value {
+        text-align: center;
+        float: right;
+        width: 70%;
+        height: 100%;
+      }
+
+      div.imageName {
+        font-weight: bold;
+        padding: 1em;
+      }
+
+      a {
+        display: block;
+      }
+
+      .ellipsis {
+        overflow: hidden;
+        text-overflow: ellipsis;
+        white-space: nowrap;
+        float: left;
+        width: 80%;
+      }
+
+      img {
+        width: 100%;
+        height: auto;
+        max-height: 100%;
+        max-width: 80%;
+        object-fit: contain;
+      }
+
+      #editButton {
+        box-shadow: none;
+      }
+    </style>
+    <div class="image-panel">
+      <div class="title">
+        <img id="thumbnail">
+      </div>
+      <div id="staging" class="value">
+        <div class="imageName">
+          <iron-input bind-value="{{imageName}}">
+            <input id="imageNameInput" value="{{imageName::input}}" type="text"
+                   disabled="[[!_editing]]" placeholder$="[[imageName]]">
+          </iron-input>
+          <gr-button id="editButton" on-click="_handleEditImage" hidden="[[_editing]]">
+            Edit
+          </gr-button>
+          <gr-button id="saveButton" on-click="_handleSaveName" hidden="[[!_editing]]">
+            Save
+          </gr-button>
+          <gr-button id="cancelRenameButton" on-click="_handleCancelRenameName"
+                     hidden="[[!_editing]]">
+            Cancel
+          </gr-button>
+        </div>
+        <div>
+          <section>
+            <gr-button id="uploadButton"
+                       on-click="_handleUploadImage"
+                       disabled="[[_editing]]">
+              Upload
+            </gr-button>
+            <gr-button id="cleanButton"
+                       on-click="_handleClearImage">
+              Clear
+            </gr-button>
+          </section>
+        </div>
+      </div>
+      <div id="uploading" class="value">
+        <div class="imageName">
+          [[imageName]]
+        </div>
+        <div>
+          <a class="ellipsis" href="[[imageUrl]]">[[imageUrl]]</a>
+          <gr-copy-clipboard has-tooltip button-title="Copy URL to Clipboard"
+                             hide-input text="[[imageUrl]]">
+          </gr-copy-clipboard>
+        </div>
+        <gr-button id="deleteButton"
+                   on-click="_openDeleteDialog">
+          Delete
+        </gr-button>
+        <gr-overlay id="deleteOverlay" with-backdrop>
+          <gr-dialog id="deleteDialog"
+                     class="confirmDialog"
+                     confirm-label="Delete"
+                     confirm-on-enter
+                     on-confirm="_handleDeleteImage">
+            <div class="header" slot="header">
+              Delete Image
+            </div>
+            <div class="main" slot="main">
+              Are you sure you want to delete '[[imageName]]'?
+            </div>
+          </gr-dialog>
+        </gr-overlay>
+      </div>
+    </div>
+  </template>
+  <script src="gr-imagare-list-item.js"></script>
+</dom-module>
diff --git a/src/main/resources/static/gr-imagare-list-item.js b/src/main/resources/static/gr-imagare-list-item.js
new file mode 100644
index 0000000..5f1cffe
--- /dev/null
+++ b/src/main/resources/static/gr-imagare-list-item.js
@@ -0,0 +1,107 @@
+// Copyright (C) 2019 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.
+
+(function () {
+  'use strict';
+
+  Polymer({
+    is: 'gr-imagare-list-item',
+
+    properties: {
+      imageUrl: {
+        type: String,
+        reflectToAttribute: true,
+      },
+      imageName: {
+        type: String,
+        reflectToAttribute: true,
+      },
+      imageData: {
+        type: String,
+        reflectToAttribute: true,
+      },
+      uploaded: {
+        type: Boolean,
+        observer: '_uploadedChanged',
+        reflectToAttribute: true,
+      },
+      _originalImageName: String,
+      _editing: {
+        type: Boolean,
+        value: false,
+      },
+      _imageSrc: String,
+    },
+
+    attached() {
+      this._originalImageName = this.imageName;
+      this._setImage();
+    },
+
+    _handleCancelRenameName() {
+      this.imageName = this._originalImageName;
+      this._editing = false;
+    },
+
+    _handleClearImage() {
+      this.fire("clear");
+    },
+
+    _handleDeleteImage() {
+      this.fire("delete");
+    },
+
+    _handleEditImage() {
+      this._editing = true;
+    },
+
+    _handleSaveName() {
+      this._editing = false;
+
+      if (this._originalImageName === this.imageName) {
+        return;
+      }
+
+      let oldFileType = this._originalImageName.split('.').pop();
+      let newFileType = this.imageName.split('.').pop();
+      if (oldFileType !== newFileType) {
+        this.imageName += `.${oldFileType}`;
+      }
+
+      this.fire("editName", {oldName: this._originalImageName, newName: this.imageName});
+    },
+
+    _handleUploadImage() {
+      this.fire("upload");
+    },
+
+    _openDeleteDialog() {
+      this.$.deleteOverlay.open();
+    },
+
+    _setImage() {
+      if (this.uploaded) {
+        this.$.thumbnail.setAttribute('src', this.imageUrl);
+      } else {
+        this.$.thumbnail.setAttribute('src', this.imageData);
+      }
+    },
+
+    _uploadedChanged(uploaded) {
+      this.$.uploading.hidden = !uploaded;
+      this.$.staging.hidden = uploaded;
+      this._setImage();
+    },
+  });
+})();
diff --git a/src/main/resources/static/gr-imagare-pref-menu-item.html b/src/main/resources/static/gr-imagare-pref-menu-item.html
new file mode 100644
index 0000000..ee8b989
--- /dev/null
+++ b/src/main/resources/static/gr-imagare-pref-menu-item.html
@@ -0,0 +1,29 @@
+<!--
+@license
+Copyright (C) 2019 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.
+-->
+
+<dom-module id="gr-imagare-pref-menu-item">
+  <template>
+    <style include="shared-styles"></style>
+    <style include="gr-page-nav-styles">
+      li {
+        padding-left: 1.5em;
+        padding-right: 1.5em;
+      }
+    </style>
+    <li class="navStyles">
+      <a href="#ImagarePreferences">Imagare Preferences</a>
+    </li>
+  </template>
+  <script src="gr-imagare-pref-menu-item.js"></script>
+</dom-module>
diff --git a/src/main/resources/static/gr-imagare-pref-menu-item.js b/src/main/resources/static/gr-imagare-pref-menu-item.js
new file mode 100644
index 0000000..3e1a7b0
--- /dev/null
+++ b/src/main/resources/static/gr-imagare-pref-menu-item.js
@@ -0,0 +1,21 @@
+// Copyright (C) 2019 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.
+
+(function () {
+  'use strict';
+
+  Polymer({
+    is: 'gr-imagare-pref-menu-item',
+  });
+})();
diff --git a/src/main/resources/static/gr-imagare-preferences.html b/src/main/resources/static/gr-imagare-preferences.html
new file mode 100644
index 0000000..ae26388
--- /dev/null
+++ b/src/main/resources/static/gr-imagare-preferences.html
@@ -0,0 +1,60 @@
+<!--
+@license
+Copyright (C) 2019 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.
+-->
+
+<dom-module id="gr-imagare-preferences">
+  <template>
+    <style include="shared-styles"></style>
+    <style include="gr-form-styles"></style>
+    <h2 id="ImagarePreferences">Imagare Preferences</h2>
+    <fieldset class="gr-form-styles">
+      <section>
+        <span class="title">Link Decoration</span>
+        <span class="value">
+          <gr-select bind-value="{{_linkDecoration}}">
+            <select on-change="_handlePrefsChanged">
+              <option value="NONE">none</option>
+              <option value="INLINE">inline</option>
+              <option value="TOOLTIP">tooltip</option>
+            </select>
+          </gr-select>
+        </span>
+      </section>
+      <section>
+        <span class="title">Default Project</span>
+        <span class="value">
+          <gr-autocomplete id="imagareDefaultProjectInput" text="{{_defaultImageProject}}"
+                           value="{{_defaultImageProject}}" query="[[_query]]"
+                           on-commit="_handlePrefsChanged" on-keyup="_handlePrefsChanged">
+            [[_defaultImageProject]]
+          </gr-autocomplete>
+        </span>
+      </section>
+      <section>
+        <span class="title">Stage images before upload</span>
+        <span class="value">
+          <input id="stageImages"
+                 type="checkbox"
+                 checked$="[[_stageImages]]"
+                 on-change="_handleStageImagesChanged">
+        </span>
+      </section>
+      <gr-button id="saveButton"
+                 on-tap="_handleImagarePrefsSave"
+                 disabled="[[!_prefsChanged]]">
+        Save Changes
+      </gr-button>
+    </fieldset>
+  </template>
+  <script src="gr-imagare-preferences.js"></script>
+</dom-module>
diff --git a/src/main/resources/static/gr-imagare-preferences.js b/src/main/resources/static/gr-imagare-preferences.js
new file mode 100644
index 0000000..d1f868f
--- /dev/null
+++ b/src/main/resources/static/gr-imagare-preferences.js
@@ -0,0 +1,101 @@
+// Copyright (C) 2019 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.
+
+(function () {
+  'use strict';
+
+  Polymer({
+    is: 'gr-imagare-preferences',
+
+    properties: {
+      _defaultImageProject: String,
+      _linkDecoration: String,
+      _stageImages: Boolean,
+      _prefsChanged: {
+        type: Boolean,
+        value: false
+      },
+      _query: {
+        type: Function,
+        value() {
+          return this._queryProjects.bind(this);
+        },
+      },
+    },
+
+    attached() {
+      this._getUserPreferences();
+    },
+
+    _getUserPreferences() {
+      this.plugin.restApi('/accounts/self/')
+        .get(`imagare~preference`)
+        .then(config => {
+          if (!config) {
+            return;
+          }
+
+          this._linkDecoration = config.link_decoration;
+          this._defaultImageProject = config.default_project;
+          this._stageImages = config.stage;
+        }).catch(response => {
+          this.fire('show-error', {message: response});
+        });
+    },
+
+    _handleImagarePrefsSave(){
+      this.plugin.restApi('/accounts/self/')
+        .put(`imagare~preference`, {
+          default_project: this._defaultImageProject,
+          link_decoration: this._linkDecoration,
+          stage: this._stageImages,
+        }).then(() => {
+          this._prefsChanged = false;
+        }).catch(response => {
+          this.fire('show-error', {message: response});
+        });
+    },
+
+    _handlePrefsChanged() {
+      this._prefsChanged = true;
+    },
+
+    _handleStageImagesChanged(event){
+      this._handlePrefsChanged();
+      this._stageImages = event.target.checked;
+    },
+
+    _queryProjects(input) {
+      let query;
+      if (!input || input === this._defaultImageProject) {
+        query = '';
+      } else {
+        query = `?prefix=${input}`;
+      }
+
+      return this.plugin.restApi('/a/projects/').get(query)
+          .then(response => {
+            const projects = [];
+            for (const key in response) {
+              if (!response.hasOwnProperty(key)) { continue; }
+              projects.push({
+                name: key,
+                value: decodeURIComponent(response[key].id),
+              });
+            }
+            return projects;
+          });
+    },
+  });
+})();
diff --git a/src/main/resources/static/gr-imagare-upload.html b/src/main/resources/static/gr-imagare-upload.html
new file mode 100644
index 0000000..814ade2
--- /dev/null
+++ b/src/main/resources/static/gr-imagare-upload.html
@@ -0,0 +1,132 @@
+<!--
+@license
+Copyright (C) 2019 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.
+-->
+
+<link rel="import"
+      href="./gr-imagare-list-item.html">
+
+<dom-module id="gr-imagare-upload">
+  <template>
+    <style include="shared-styles"></style>
+    <style include="gr-subpage-styles"></style>
+    <style include="gr-form-styles"></style>
+    <style>
+      div.image-upload {
+        margin: 2em auto;
+        max-width: 50em;
+      }
+
+      h1#title {
+        margin-bottom: 1em;
+      }
+
+      div#dragDropArea {
+        padding: 2em 10em;
+        border: 2px dashed #ccc;
+        border-radius: 1em;
+        text-align: center;
+      }
+
+      div#dragDropArea>p {
+        font-weight: bold;
+        text-transform: uppercase;
+        padding: 0.25em;
+      }
+
+      div#dragDropArea>p.or {
+        color: #ccc;
+      }
+
+      input#imagareImagePathInput {
+        border: 0;
+        clip: rect(0, 0, 0, 0);
+        height: 1px;
+        overflow: hidden;
+        padding: 0;
+        position: absolute !important;
+        white-space: nowrap;
+        width: 1px;
+      }
+    </style>
+    <div class="image-upload">
+      <main class="gr-form-styles read-only">
+        <h1 id="title">Image Upload</h1>
+        <div id="loading"
+             class$="[[_computeLoadingClass(_loading)]]">
+          Loading...
+        </div>
+        <div id="form"
+             class$="[[_computeLoadingClass(_loading)]]">
+          <fieldset>
+            <h2>Settings</h2>
+            <section>
+              The user preferences for the image upload can be changed
+              <a href="[[_computeSettingsUrl()]]">here</a>.
+            </section>
+            <section>
+              <span class="title">Project</span>
+              <span class="value">
+                <gr-autocomplete id="imagareProjectInput" text="{{_imageProject}}"
+                                 value="{{_imageProject}}" query="[[_query]]">
+                  [[_defaultImageProject]]
+                </gr-autocomplete>
+              </span>
+            </section>
+          </fieldset>
+          <fieldset>
+            <h2>Image Selection</h2>
+            <section>
+              <div id="dragDropArea" contenteditable="true" on-paste="_handlePaste"
+                   on-drop="_handleDrop" on-keypress="_handleKeyPress">
+                <p>Drag and drop image here</p>
+                <p class="or">or</p>
+                <p>paste it here</p>
+                <p class="or">or</p>
+                <p>
+                  <iron-input>
+                    <input id="imagareImagePathInput"
+                           type="file"
+                           on-change="_handleImagePathChanged"
+                           slot="input"
+                           multiple>
+                  </iron-input>
+                  <label for="imagareImagePathInput">
+                    <gr-button>
+                      Browse
+                    </gr-button>
+                  </label>
+                </p>
+              </div>
+            </section>
+          </fieldset>
+          <fieldset id="imageListContainer" hidden>
+            <h2>Images</h2>
+            <fieldset id="imageList"></fieldset>
+            <section>
+              <gr-button id="uploadButton"
+                         on-click="_handleUploadAllImages"
+                         disabled="[[_allUploaded]]">
+                Upload All
+              </gr-button>
+              <gr-button id="cleanButton"
+                         on-click="_handleClearAllImages">
+                Clear List
+              </gr-button>
+            </section>
+          </fieldset>
+        </div>
+      </main>
+    </div>
+  </template>
+  <script src="gr-imagare-upload.js"></script>
+</dom-module>
diff --git a/src/main/resources/static/gr-imagare-upload.js b/src/main/resources/static/gr-imagare-upload.js
new file mode 100644
index 0000000..40150c1
--- /dev/null
+++ b/src/main/resources/static/gr-imagare-upload.js
@@ -0,0 +1,374 @@
+// Copyright (C) 2019 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.
+
+(function () {
+  'use strict';
+
+  function preventDefaultFn(event) {
+    event.preventDefault();
+  }
+
+  Polymer({
+    is: 'gr-imagare-upload',
+
+    properties: {
+      _loading: {
+        type: Boolean,
+        value: true,
+      },
+      _allUploaded: {
+        type: Boolean,
+        value: false,
+      },
+      _imageProject: String,
+      _defaultImageProject: String,
+      _images: {
+        type: Map,
+        value: () => new Map(),
+      },
+      _stageImages: {
+        type: Boolean,
+        value: true,
+      },
+      _undefinedFileCounter: {
+        type: Number,
+        value: 0,
+      },
+      _query: {
+        type: Function,
+        value() {
+          return this._queryProjects.bind(this);
+        },
+      },
+    },
+
+    listeners: {
+      clear: '_handleClearImage',
+      delete: '_handleDeleteImage',
+      editName: '_handleEditImageName',
+      upload: '_handleUploadImage',
+    },
+
+    attached() {
+      this.fire('title-change', { title: 'Image Upload' });
+
+      window.addEventListener('dragover', preventDefaultFn, false);
+      window.addEventListener('drop', preventDefaultFn, false);
+      this.$.dragDropArea.addEventListener('paste', preventDefaultFn, false);
+
+      this._getUserPreferences();
+    },
+
+    detached() {
+      window.removeEventListener('dragover', preventDefaultFn, false);
+      window.removeEventListener('drop', preventDefaultFn, false);
+      this.$.dragDropArea.removeEventListener('paste', preventDefaultFn, false);
+    },
+
+    _computeFilenameWithCorrectType(filedata, filename) {
+      let realFiletype = filedata.slice(
+        filedata.indexOf('/') + 1,
+        filedata.indexOf(';'));
+
+      let givenFiletype;
+
+      if (filename.indexOf(".") !== -1) {
+        givenFiletype = filename.split('.').pop();
+      }
+
+      if (!givenFiletype || realFiletype !== givenFiletype) {
+        filename += `.${realFiletype}`;
+      }
+
+      return filename;
+    },
+
+    _computeLoadingClass(loading) {
+      return loading ? 'loading' : '';
+    },
+
+    _computeSettingsUrl() {
+      return `${location.origin}/settings#ImagarePreferences`;
+    },
+
+    _computeUploadAllDisabled() {
+      if (this._images) {
+        for (let value of this._images.values()) {
+          if (!value.uploaded) {
+            this._allUploaded = false;
+            return;
+          }
+        }
+      }
+
+      this._allUploaded = true;
+    },
+
+    _createImageObject(name, data, url, list_entry, uploaded, ref) {
+      return {
+        data: data,
+        list_entry: list_entry,
+        name: name,
+        ref: ref,
+        url: url,
+        uploaded: uploaded,
+      }
+    },
+
+    _createListEntry(name, data, url) {
+      let imagePanel = document.createElement('gr-imagare-list-item');
+      imagePanel.setAttribute("image-name", name);
+
+      if (data) {
+        imagePanel.setAttribute("image-data", data);
+      }
+
+      if (url) {
+        imagePanel.setAttribute("image-url", url);
+        imagePanel.uploaded = true;
+      } else {
+        imagePanel.uploaded = false;
+      }
+
+      this.$.imageList.appendChild(imagePanel);
+
+      return imagePanel;
+    },
+
+    _deleteImage(image) {
+      this.plugin.restApi('/projects')
+        .delete(`/${this._imageProject}/imagare~images/${image.ref}`)
+        .then(() => {
+          image.list_entry.remove();
+          this._images.delete(image.name);
+          if (!this.$.imageList.hasChildNodes()) {
+            this.$.imageListContainer.hidden = true;
+          }
+        }).catch(response => {
+          this.fire('show-error', { message: response });
+        });
+    },
+
+    _extractImageRef(url) {
+      return url.split('/').slice(-2)[0];
+    },
+
+    _getUserPreferences() {
+      this.plugin.restApi('/accounts/self/')
+        .get(`imagare~preference`)
+        .then(config => {
+          if (!config) {
+            return;
+          }
+
+          this._defaultImageProject = config.default_project;
+          this._imageProject = config.default_project;
+          this._stageImages = config.stage;
+          this._loading = false;
+        });
+    },
+
+    _handleClearAllImages() {
+      while (this.$.imageList.firstChild) {
+        this.$.imageList.removeChild(this.$.imageList.firstChild);
+      }
+      this.$.imageListContainer.hidden = true;
+
+      this._images.clear()
+    },
+
+    _handleClearImage(event) {
+      event.stopPropagation();
+      this._images.delete(event.target.imageName);
+      event.target.remove();
+      if (!this.$.imageList.hasChildNodes()) {
+        this.$.imageListContainer.hidden = true;
+      }
+    },
+
+    _handleDeleteImage(event) {
+      event.stopPropagation();
+      this._deleteImage(this._images.get(event.target.imageName));
+    },
+
+    _handleDrop(event) {
+      event.preventDefault();
+      event.stopPropagation();
+
+      for (let file of event.dataTransfer.files) {
+        if (!file.type.match('image/.*')) {
+          this.fire('show-error', { message: `No image file: ${file.name}` });
+        }
+        let fr = new FileReader();
+        fr.file = file;
+        fr.onload = fileLoadEvent => this._handleFileLoadEvent(
+          fr.file.name, fileLoadEvent);
+        fr.readAsDataURL(file);
+      }
+    },
+
+    _handleEditImageName(event) {
+      event.stopPropagation();
+      let editedImage = this._images.get(event.detail.oldName);
+      if (this._images.has(event.detail.newName)) {
+        this.fire('show-error', { message: 'An image with the same name was already staged.' });
+        editedImage.list_entry.setAttribute("image-name", event.detail.oldName);
+      } else {
+        editedImage.name = event.detail.newName;
+        this._images.set(editedImage.name, editedImage);
+        this._images.delete(event.detail.oldName);
+      }
+    },
+
+    _handleFileLoadEvent(filename, event) {
+      let correctedFilename = this._computeFilenameWithCorrectType(
+        event.target.result, filename);
+      if (this._stageImages) {
+        this._stageImage(correctedFilename, event.target.result);
+      } else {
+        let image = this._createImageObject(correctedFilename, event.target.result);
+        this._images.set(correctedFilename, image);
+        this._uploadImage(image);
+      }
+    },
+
+    _handleKeyPress(event) {
+      let ctrlDown = event.ctrlKey || event.metaKey;
+      if (!ctrlDown) {
+        event.preventDefault();
+        event.stopPropagation();
+      }
+    },
+
+    _handleImagePathChanged(event) {
+      for (let file of event.target.files) {
+        let fr = new FileReader();
+        fr.file = file;
+        fr.onload = fileLoadEvent => this._handleFileLoadEvent(
+          fr.file.name, fileLoadEvent);
+        fr.readAsDataURL(file);
+      }
+
+      event.target.value = '';
+    },
+
+    _handlePaste(event) {
+      let clipboardData = event.clipboardData || event.originalEvent.clipboardData;
+      let items = clipboardData.items;
+      if (JSON.stringify(items)) {
+        let blob;
+        for (let item of items) {
+          if (item.type.indexOf("image") === 0) {
+            blob = item.getAsFile();
+          }
+        }
+        if (blob) {
+          let fr = new FileReader();
+          fr.onload = fileLoadEvent => {
+            let filename = `undefined-${this._undefinedFileCounter}`;
+            this._undefinedFileCounter++;
+            this._handleFileLoadEvent(filename, fileLoadEvent);
+          };
+          fr.readAsDataURL(blob);
+        } else {
+          event.preventDefault();
+          this.fire('show-error', { message: `No image file` });
+        }
+      }
+    },
+
+    _handleUploadAllImages() {
+      for (let image of this._images.values()) {
+        this._uploadImage(image);
+      }
+    },
+
+    _handleUploadImage(event) {
+      event.stopPropagation();
+      let image = this._createImageObject(
+        event.target.imageName,
+        event.target.imageData,
+        null,
+        event.target);
+      this._images.set(image.name, image);
+      this._uploadImage(image);
+    },
+
+    _queryProjects(input) {
+      let query;
+      if (!input || input === this._defaultImageProject) {
+        query = '';
+      } else {
+        query = `?prefix=${input}`;
+      }
+
+      return this.plugin.restApi('/a/projects/').get(query)
+        .then(response => {
+          const projects = [];
+          for (const key in response) {
+            if (!response.hasOwnProperty(key)) { continue; }
+            projects.push({
+              name: key,
+              value: decodeURIComponent(response[key].id),
+            });
+          }
+          return projects;
+        });
+    },
+
+    _stageImage(name, data) {
+      if (this._images.has(name)) {
+        let fileName = name.slice(0, name.lastIndexOf('.'));
+        let fileExtension = name.slice(name.lastIndexOf('.'));
+        name = `${fileName}-${this._undefinedFileCounter}${fileExtension}`;
+        this._undefinedFileCounter++;
+      }
+      let imagePanel = this._createListEntry(name, data, null);
+      this._images.set(name, this._createImageObject(name, data, null, imagePanel));
+      this.$.imageListContainer.hidden = false;
+      this._computeUploadAllDisabled();
+    },
+
+    _uploadImage(image) {
+      if (image && image.uploaded) {
+        return;
+      }
+
+      this.plugin.restApi('/projects')
+        .post(`/${this._imageProject}/imagare~images`, {
+          image_data: image.data,
+          file_name: image.name,
+        })
+        .then(response => {
+          if (!image.list_entry) {
+            image.list_entry = this._createListEntry(image.name, image.data, response.url);
+          } else {
+            image.list_entry.setAttribute("image-url", response.url);
+            image.list_entry.uploaded = true;
+          }
+
+          this._images.set(
+            image.name,
+            this._createImageObject(
+              image.name, image.data, response.url, image.list_entry, true,
+              this._extractImageRef(response.url)));
+
+          this.$.imageListContainer.hidden = false;
+          this._computeUploadAllDisabled();
+        }).catch(response => {
+          this.fire('show-error', { message: response });
+        });
+    }
+  });
+})();
diff --git a/src/main/resources/static/imagare.html b/src/main/resources/static/imagare.html
new file mode 100644
index 0000000..2d2077d
--- /dev/null
+++ b/src/main/resources/static/imagare.html
@@ -0,0 +1,38 @@
+<!--
+@license
+Copyright (C) 2019 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.
+-->
+
+<link rel="import" href="./gr-imagare-inline.html">
+<link rel="import" href="./gr-imagare-preferences.html">
+<link rel="import" href="./gr-imagare-pref-menu-item.html">
+<link rel="import" href="./gr-imagare-upload.html">
+
+<dom-module id="imagare">
+  <script>
+    Gerrit.install(plugin => {
+      if (!window.Polymer) { return; }
+
+      plugin.restApi('/config/server/').get('imagare~config').then(config => {
+        if (config && config.enable_image_server) {
+          plugin.screen('upload', 'gr-imagare-upload');
+        }
+      });
+      plugin.registerCustomComponent('change-view-integration', 'gr-imagare-inline');
+      plugin.registerCustomComponent('settings-screen', 'gr-imagare-preferences');
+      plugin.registerCustomComponent('settings-menu-item', 'gr-imagare-pref-menu-item');
+    });
+  </script>
+</dom-module>
diff --git a/src/main/resources/static/imagare.js b/src/main/resources/static/imagare.js
index 91bec6f..779f1b5 100644
--- a/src/main/resources/static/imagare.js
+++ b/src/main/resources/static/imagare.js
@@ -13,6 +13,11 @@
 // limitations under the License.
 
 Gerrit.install(function(self) {
+
+    if (window.Polymer) { return; }
+
+    // The code below is only used by the GWT-UI
+
     function onComment(e) {
       var prefs = getPrefsFromCookie();
       if (prefs !== null) {