Allow plugins to add custom settings screens

Plugins can now add custom screens which are integrated into the
Gerrit user settings menu. A new entry in the user settings menu loads
and displays the settings screen from the plugin. The user settings
menu on the left side is automatically included into the custom
settings screens.

Plugins must register settings screen early at plugin module load. The
way how settings screens are registered is similar to how plugins can
add normal custom screens.

An example settings screen was implemented in the cookbook plugin.

Change-Id: I4a88e4975ff436a5cdb125ebbde276dfab280ce2
Signed-off-by: Edwin Kempin <edwin.kempin@sap.com>
This commit is contained in:
Edwin Kempin
2015-07-08 13:28:40 +02:00
parent 2a8c515a27
commit 70e1112c0d
10 changed files with 222 additions and 1 deletions

View File

@@ -1730,6 +1730,31 @@ public class MyPlugin extends PluginEntryPoint {
}
----
[[user-settings-screen]]
== Add User Settings Screen
A link:#gwt_ui_extension[GWT plugin] can implement a user settings
screen that is integrated into the Gerrit user settings menu.
Example settings screen:
[source,java]
----
public class MyPlugin extends PluginEntryPoint {
@Override
public void onPluginLoad() {
Plugin.get().settingsScreen("my-preferences", "My Preferences",
new Screen.EntryPoint() {
@Override
public void onLoad(Screen screen) {
screen.setPageTitle("Settings");
screen.add(new InlineLabel("My Preferences"));
screen.show();
}
});
}
}
----
[[settings-screen]]
== Plugin Settings Screen

View File

@@ -33,6 +33,7 @@ public class PageLinks {
public static final String SETTINGS_CONTACT = "/settings/contact";
public static final String SETTINGS_PROJECTS = "/settings/projects";
public static final String SETTINGS_NEW_AGREEMENT = "/settings/new-agreement";
public static final String SETTINGS_EXTENSION = "/settings/x/";
public static final String REGISTER = "/register";
public static final String MINE = "/";
@@ -128,6 +129,10 @@ public class PageLinks {
return ADMIN_GROUPS + "uuid-" + uuid;
}
public static String toSettings(String pluginName, String path) {
return SETTINGS_EXTENSION + pluginName + "/" + path;
}
private static String status(Status status) {
switch (status) {
case ABANDONED:

View File

@@ -28,6 +28,7 @@ import static com.google.gerrit.common.PageLinks.REGISTER;
import static com.google.gerrit.common.PageLinks.SETTINGS;
import static com.google.gerrit.common.PageLinks.SETTINGS_AGREEMENTS;
import static com.google.gerrit.common.PageLinks.SETTINGS_CONTACT;
import static com.google.gerrit.common.PageLinks.SETTINGS_EXTENSION;
import static com.google.gerrit.common.PageLinks.SETTINGS_HTTP_PASSWORD;
import static com.google.gerrit.common.PageLinks.SETTINGS_MYGROUPS;
import static com.google.gerrit.common.PageLinks.SETTINGS_NEW_AGREEMENT;
@@ -65,6 +66,7 @@ import com.google.gerrit.client.admin.ProjectInfoScreen;
import com.google.gerrit.client.admin.ProjectListScreen;
import com.google.gerrit.client.admin.ProjectScreen;
import com.google.gerrit.client.api.ExtensionScreen;
import com.google.gerrit.client.api.ExtensionSettingsScreen;
import com.google.gerrit.client.change.ChangeScreen;
import com.google.gerrit.client.change.FileTable;
import com.google.gerrit.client.changes.AccountDashboardScreen;
@@ -720,6 +722,16 @@ public class Dispatcher {
return new NewAgreementScreen(skip(token));
}
if (matchPrefix(SETTINGS_EXTENSION, token)) {
ExtensionSettingsScreen view =
new ExtensionSettingsScreen(skip(token));
if (view.isFound()) {
return view;
} else {
return new NotFoundScreen();
}
}
return new NotFoundScreen();
}
});

View File

@@ -15,6 +15,8 @@
package com.google.gerrit.client.account;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.api.ExtensionSettingsScreen;
import com.google.gerrit.client.rpc.Natives;
import com.google.gerrit.client.ui.MenuScreen;
import com.google.gerrit.common.PageLinks;
@@ -37,6 +39,13 @@ public abstract class SettingsScreen extends MenuScreen {
if (Gerrit.info().auth().useContributorAgreements()) {
link(Util.C.tabAgreements(), PageLinks.SETTINGS_AGREEMENTS);
}
for (String pluginName : ExtensionSettingsScreen.Definition.plugins()) {
for (ExtensionSettingsScreen.Definition def :
Natives.asList(ExtensionSettingsScreen.Definition.get(pluginName))) {
link(def.getMenu(), PageLinks.toSettings(pluginName, def.getPath()));
}
}
}
@Override

View File

@@ -35,12 +35,14 @@ public class ApiGlue {
private static native void init0() /*-{
var serverUrl = @com.google.gwt.core.client.GWT::getHostPageBaseURL()();
var ScreenDefinition = @com.google.gerrit.client.api.ExtensionScreen.Definition::TYPE;
var SettingsScreenDefinition = @com.google.gerrit.client.api.ExtensionSettingsScreen.Definition::TYPE;
var PanelDefinition = @com.google.gerrit.client.api.ExtensionPanel.Definition::TYPE;
$wnd.Gerrit = {
JsonString: @com.google.gerrit.client.rpc.NativeString::TYPE,
events: {},
plugins: {},
screens: {},
settingsScreens: {},
panels: {},
change_actions: {},
edit_actions: {},
@@ -87,6 +89,11 @@ public class ApiGlue {
var s = new ScreenDefinition(r,c);
(this.screens[p] || (this.screens[p]=[])).push(s);
},
settingsScreen: function(p,m,c){this._settingsScreen(this.getPluginName(),p,m,c)},
_settingsScreen: function(n,p,m,c){
var s = new SettingsScreenDefinition(p,m,c);
(this.settingsScreens[n] || (this.settingsScreens[n]=[])).push(s);
},
panel: function(i,c){this._panel(this.getPluginName(),i,c)},
_panel: function(n,i,c){
var p = new PanelDefinition(n,c);

View File

@@ -0,0 +1,144 @@
// Copyright (C) 2015 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.client.api;
import com.google.gerrit.client.account.SettingsScreen;
import com.google.gerrit.client.rpc.NativeMap;
import com.google.gerrit.client.rpc.NativeString;
import com.google.gerrit.client.rpc.Natives;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.JsArray;
import com.google.gwt.dom.client.Element;
import java.util.Set;
/** SettingsScreen contributed by a plugin. */
public class ExtensionSettingsScreen extends SettingsScreen {
private Context ctx;
public ExtensionSettingsScreen(String token) {
if (token.contains("?")) {
token = token.substring(0, token.indexOf('?'));
}
String name;
String rest;
int s = token.indexOf('/');
if (0 < s) {
name = token.substring(0, s);
rest = token.substring(s + 1);
} else {
name = token;
rest = "";
}
ctx = create(name, rest);
}
private Context create(String name, String rest) {
for (Definition def : Natives.asList(Definition.get(name))) {
if (def.matches(rest)) {
return Context.create(def, this);
}
}
return null;
}
public boolean isFound() {
return ctx != null;
}
@Override
protected void onLoad() {
super.onLoad();
setHeaderVisible(false);
ctx.onLoad();
}
@Override
protected void onUnload() {
super.onUnload();
for (JavaScriptObject u : Natives.asList(ctx.unload())) {
ApiGlue.invoke(u);
}
}
public static class Definition extends JavaScriptObject {
static final JavaScriptObject TYPE = init();
private static native JavaScriptObject init() /*-{
function SettingsScreenDefinition(p, m, c) {
this.path = p;
this.menu = m;
this.onLoad = c;
};
return SettingsScreenDefinition;
}-*/;
public static native JsArray<Definition> get(String n)
/*-{ return $wnd.Gerrit.settingsScreens[n] || [] }-*/;
public static final Set<String> plugins() {
return Natives.keys(settingsScreens());
}
private static final native NativeMap<NativeString> settingsScreens()
/*-{ return $wnd.Gerrit.settingsScreens; }-*/;
public final native String getPath() /*-{ return this.path; }-*/;
public final native String getMenu() /*-{ return this.menu; }-*/;
final native boolean matches(String t)
/*-{ return this.path == t; }-*/;
protected Definition() {
}
}
static class Context extends JavaScriptObject {
static final Context create(
Definition def,
ExtensionSettingsScreen view) {
return create(TYPE, def, view, view.getBody().getElement());
}
final native void onLoad() /*-{ this._d.onLoad(this) }-*/;
final native JsArray<JavaScriptObject> unload() /*-{ return this._u }-*/;
private static final native Context create(
JavaScriptObject T,
Definition d,
ExtensionSettingsScreen s,
Element e)
/*-{ return new T(d,s,e) }-*/;
private static final JavaScriptObject TYPE = init();
private static final native JavaScriptObject init() /*-{
var T = function(d,s,e) {
this._d = d;
this._s = s;
this._u = [];
this.body = e;
};
T.prototype = {
setTitle: function(t){this._s.@com.google.gerrit.client.ui.Screen::setPageTitle(Ljava/lang/String;)(t)},
setWindowTitle: function(t){this._s.@com.google.gerrit.client.ui.Screen::setWindowTitle(Ljava/lang/String;)(t)},
show: function(){$entry(this._s.@com.google.gwtexpui.user.client.View::display()())},
onUnload: function(f){this._u.push(f)},
};
return T;
}-*/;
protected Context() {
}
}
}

View File

@@ -59,6 +59,7 @@ final class Plugin extends JavaScriptObject {
on: function(e,f){G.on(e,f)},
onAction: function(t,n,c){G._onAction(this.name,t,n,c)},
screen: function(p,c){G._screen(this.name,p,c)},
settingsScreen: function(p,m,c){G._settingsScreen(this.name,p,m,c)},
panel: function(i,c){G._panel(this.name,i,c)},
url: function (u){return G.url(this._url(u))},

View File

@@ -48,6 +48,11 @@ public abstract class MenuScreen extends Screen {
super.setToken(token);
}
@Override
protected FlowPanel getBody() {
return body;
}
@Override
protected void add(final Widget w) {
body.add(w);

View File

@@ -93,6 +93,19 @@ public final class Plugin extends JavaScriptObject {
private final native void screenRegex(String p, JavaScriptObject e)
/*-{ this.screen(new $wnd.RegExp(p), e) }-*/;
/**
* Register a settings screen displayed at {@code /#/settings/x/plugin/token}.
*
* @param token literal anchor token appearing after the plugin name.
* @param entry callback function invoked to create the settings screen widgets.
*/
public final void settingsScreen(String token, String menu, Screen.EntryPoint entry) {
settingsScreen(token, menu, wrap(entry));
}
private final native void settingsScreen(String t, String m, JavaScriptObject e)
/*-{ this.settingsScreen(t, m, e) }-*/;
/**
* Register a panel for a UI extension point.
*