resty-generators 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/generators/resty/base.rb +33 -5
- data/lib/generators/resty/controller/controller_generator.rb +2 -2
- data/lib/generators/resty/model/model_generator.rb +1 -1
- data/lib/generators/resty/scaffold/scaffold_generator.rb +76 -4
- data/lib/generators/resty/setup/setup_generator.rb +141 -4
- data/lib/generators/resty/setup/templates/ActivityFactory.java +12 -0
- data/lib/generators/resty/setup/templates/ActivityPlace.java +25 -0
- data/lib/generators/resty/setup/templates/ActivityPlaceActivityMapper.java +30 -0
- data/lib/generators/resty/setup/templates/BreadCrumbsPanel.java +80 -0
- data/lib/generators/resty/setup/templates/EntryPoint.java +75 -4
- data/lib/generators/resty/setup/templates/GinModule.java +40 -0
- data/lib/generators/resty/setup/templates/LoginActivity.java +59 -0
- data/lib/generators/resty/setup/templates/LoginPlace.java +20 -0
- data/lib/generators/resty/setup/templates/LoginView.ui.xml +18 -0
- data/lib/generators/resty/setup/templates/LoginViewImpl.java +21 -0
- data/lib/generators/resty/setup/templates/Mavenfile +7 -4
- data/lib/generators/resty/setup/templates/PlaceHistoryMapper.java +8 -0
- data/lib/generators/resty/setup/templates/RestfulPlace.java +24 -0
- data/lib/generators/resty/setup/templates/SessionActivityPlaceActivityMapper.java +71 -0
- data/lib/generators/resty/setup/templates/SessionRestService.java +23 -0
- data/lib/generators/resty/setup/templates/User.java +8 -0
- data/lib/generators/resty/setup/templates/authentication.rb +3 -0
- data/lib/generators/resty/setup/templates/empty.css +17 -0
- data/lib/generators/resty/setup/templates/gitignore +3 -0
- data/lib/generators/resty/setup/templates/group.rb +14 -0
- data/lib/generators/resty/setup/templates/gwt.css +17 -0
- data/lib/generators/resty/setup/templates/module.gwt.xml +9 -2
- data/lib/generators/resty/setup/templates/page.html +1 -1
- data/lib/generators/resty/setup/templates/session.rb +45 -0
- data/lib/generators/resty/setup/templates/sessions_controller.rb +29 -0
- data/lib/generators/resty/setup/templates/user.rb +28 -0
- data/lib/generators/resty/setup/templates/web.xml +50 -0
- data/lib/generators/resty/templates/Activity.java +140 -0
- data/lib/generators/resty/templates/ColumnDefinitionsImpl.java +24 -0
- data/lib/generators/resty/templates/Model.java +13 -7
- data/lib/generators/resty/templates/Place.java +27 -0
- data/lib/generators/resty/templates/PlaceTokenizer.java +25 -0
- data/lib/generators/resty/templates/{Controller.java → RestService.java} +16 -12
- data/lib/generators/resty/templates/View.java +35 -0
- data/lib/generators/resty/templates/View.ui.xml +47 -0
- data/lib/generators/resty/templates/ViewImpl.java +196 -0
- data/lib/resty-generators.rb +1 -0
- data/lib/resty/child_path.rb +17 -4
- data/lib/resty/resty_railtie.rb +4 -5
- metadata +70 -60
@@ -1,19 +1,90 @@
|
|
1
1
|
package <%= base_package %>;
|
2
2
|
|
3
|
+
import <%= managed_package %>.<%= application_name %>PlaceHistoryMapper;
|
4
|
+
import <%= managed_package %>.<%= application_name %>Module;
|
5
|
+
|
6
|
+
import com.google.gwt.activity.shared.ActivityManager;
|
3
7
|
import com.google.gwt.core.client.EntryPoint;
|
4
|
-
import com.google.gwt.
|
8
|
+
import com.google.gwt.core.client.GWT;
|
9
|
+
import com.google.gwt.event.shared.EventBus;
|
10
|
+
import com.google.gwt.inject.client.GinModules;
|
11
|
+
import com.google.gwt.inject.client.Ginjector;
|
12
|
+
import com.google.gwt.place.shared.Place;
|
13
|
+
import com.google.gwt.place.shared.PlaceController;
|
14
|
+
import com.google.gwt.place.shared.PlaceHistoryHandler;
|
15
|
+
import com.google.gwt.user.client.ui.Panel;
|
5
16
|
import com.google.gwt.user.client.ui.RootPanel;
|
17
|
+
import com.google.inject.Inject;
|
18
|
+
|
19
|
+
import <%= gwt_rails_package %>.Application;
|
20
|
+
import <%= gwt_rails_package %>.Notice;
|
21
|
+
import <%= gwt_rails_package %>.DefaultDispatcherSingleton;
|
22
|
+
|
23
|
+
import org.fusesource.restygwt.client.Defaults;
|
6
24
|
|
7
25
|
/**
|
8
26
|
* Entry point classes define <code>onModuleLoad()</code>.
|
9
27
|
*/
|
10
|
-
public class <%= application_name %> implements EntryPoint {
|
28
|
+
public class <%= application_name %>EntryPoint implements EntryPoint {
|
29
|
+
|
30
|
+
@GinModules(<%= application_name %>Module.class)
|
31
|
+
static public interface <%= application_name %>Ginjector extends Ginjector {
|
32
|
+
PlaceController getPlaceController();
|
33
|
+
EventBus getEventBus();
|
34
|
+
Application getApplication();
|
35
|
+
}
|
36
|
+
|
37
|
+
static public class <%= application_name %>Application extends Application {
|
38
|
+
private final Notice notice;
|
39
|
+
<% if options[:session] -%>
|
40
|
+
private final BreadCrumbsPanel breadCrumbs;
|
41
|
+
<% end -%>
|
42
|
+
private RootPanel root;
|
43
|
+
|
44
|
+
@Inject
|
45
|
+
<%= application_name %>Application(final Notice notice,
|
46
|
+
<% if options[:session] -%>
|
47
|
+
final BreadCrumbsPanel breadCrumbs,
|
48
|
+
<% end -%>
|
49
|
+
final ActivityManager activityManager){
|
50
|
+
super(activityManager);
|
51
|
+
this.notice = notice;
|
52
|
+
<% if options[:session] -%>
|
53
|
+
this.breadCrumbs = breadCrumbs;
|
54
|
+
<% end -%>
|
55
|
+
}
|
56
|
+
|
57
|
+
protected Panel getApplicationPanel(){
|
58
|
+
if (this.root == null) {
|
59
|
+
this.root = RootPanel.get();
|
60
|
+
this.root.add(notice);
|
61
|
+
<% if options[:session] -%>
|
62
|
+
this.root.add(breadCrumbs);
|
63
|
+
<% end -%>
|
64
|
+
}
|
65
|
+
return this.root;
|
66
|
+
}
|
67
|
+
}
|
11
68
|
|
12
69
|
/**
|
13
70
|
* This is the entry point method.
|
14
71
|
*/
|
15
72
|
public void onModuleLoad() {
|
16
|
-
|
17
|
-
|
73
|
+
Defaults.setServiceRoot(GWT.getModuleBaseURL().replaceFirst("[a-z]+/$", ""));
|
74
|
+
Defaults.setDispatcher(DefaultDispatcherSingleton.INSTANCE);
|
75
|
+
GWT.log("base url for restservices: " + Defaults.getServiceRoot());
|
76
|
+
|
77
|
+
final <%= application_name %>Ginjector injector = GWT.create(<%= application_name %>Ginjector.class);
|
78
|
+
|
79
|
+
injector.getApplication().run();
|
18
80
|
|
81
|
+
// Start PlaceHistoryHandler with our PlaceHistoryMapper
|
82
|
+
<%= application_name %>PlaceHistoryMapper historyMapper = GWT.create(<%= application_name %>PlaceHistoryMapper.class);
|
83
|
+
|
84
|
+
PlaceHistoryHandler historyHandler = new PlaceHistoryHandler(historyMapper);
|
85
|
+
historyHandler.register(injector.getPlaceController(), injector.getEventBus(), Place.NOWHERE);
|
86
|
+
|
87
|
+
// Goes to the place represented on URL else default place
|
88
|
+
historyHandler.handleCurrentHistory();
|
89
|
+
}
|
19
90
|
}
|
@@ -0,0 +1,40 @@
|
|
1
|
+
package <%= managed_package %>;
|
2
|
+
|
3
|
+
import <%= base_package %>.<%= application_name %>EntryPoint.<%= application_name %>Application;
|
4
|
+
import <%= base_package %>.<% if options[:session] -%>Session<% end -%>ActivityPlaceActivityMapper;
|
5
|
+
<% if options[:session] -%>
|
6
|
+
import <%= activities_package %>.LoginActivity;
|
7
|
+
<% end -%>
|
8
|
+
import <%= gwt_rails_package %>.Application;
|
9
|
+
import <%= gwt_rails_package %>.BaseModule;
|
10
|
+
|
11
|
+
import com.google.gwt.activity.shared.Activity;
|
12
|
+
import com.google.gwt.activity.shared.ActivityMapper;
|
13
|
+
import com.google.gwt.core.client.GWT;
|
14
|
+
import com.google.gwt.inject.client.assistedinject.GinFactoryModuleBuilder;
|
15
|
+
import com.google.inject.Provider;
|
16
|
+
import com.google.inject.Singleton;
|
17
|
+
import com.google.inject.name.Names;
|
18
|
+
|
19
|
+
<% if options[:session] -%>
|
20
|
+
import <%= views_package %>.LoginViewImpl;
|
21
|
+
|
22
|
+
import <%= gwt_rails_session_package %>.LoginView;
|
23
|
+
<% end -%>
|
24
|
+
public class <%= application_name %>Module extends BaseModule {
|
25
|
+
|
26
|
+
@Override
|
27
|
+
protected void configure() {
|
28
|
+
super.configure();
|
29
|
+
bind(Application.class).to(<%= application_name %>Application.class);
|
30
|
+
bind(ActivityMapper.class).to(<% if options[:session] -%>Session<% end -%>ActivityPlaceActivityMapper.class).in(Singleton.class);
|
31
|
+
<% if options[:session] -%>
|
32
|
+
bind(LoginView.class).to(LoginViewImpl.class);
|
33
|
+
<% end -%>
|
34
|
+
install(new GinFactoryModuleBuilder()
|
35
|
+
<% if options[:session] -%>
|
36
|
+
.implement(Activity.class, Names.named("login"), LoginActivity.class)
|
37
|
+
<% end -%>
|
38
|
+
.build(ActivityFactory.class));
|
39
|
+
}
|
40
|
+
}
|
@@ -0,0 +1,59 @@
|
|
1
|
+
package <%= activities_package %>;
|
2
|
+
|
3
|
+
import javax.inject.Inject;
|
4
|
+
|
5
|
+
import <%= models_package %>.User;
|
6
|
+
import <%= places_package %>.LoginPlace;
|
7
|
+
import <%= restservices_package %>.SessionRestService;
|
8
|
+
|
9
|
+
import org.fusesource.restygwt.client.Method;
|
10
|
+
import org.fusesource.restygwt.client.MethodCallback;
|
11
|
+
|
12
|
+
import com.google.gwt.activity.shared.AbstractActivity;
|
13
|
+
import com.google.gwt.core.client.GWT;
|
14
|
+
import com.google.gwt.event.shared.EventBus;
|
15
|
+
import com.google.gwt.user.client.ui.AcceptsOneWidget;
|
16
|
+
import com.google.inject.assistedinject.Assisted;
|
17
|
+
|
18
|
+
import <%= gwt_rails_session_package %>.Authentication;
|
19
|
+
import <%= gwt_rails_session_package %>.LoginView;
|
20
|
+
import <%= gwt_rails_session_package %>.Session;
|
21
|
+
import <%= gwt_rails_session_package %>.SessionManager;
|
22
|
+
|
23
|
+
public class LoginActivity extends AbstractActivity implements LoginView.Presenter{
|
24
|
+
|
25
|
+
private final SessionRestService service;
|
26
|
+
private final LoginView view;
|
27
|
+
private final SessionManager<User> sessionManager;
|
28
|
+
|
29
|
+
@Inject
|
30
|
+
public LoginActivity(@Assisted LoginPlace place,
|
31
|
+
LoginView view,
|
32
|
+
SessionRestService service,
|
33
|
+
SessionManager<User> sessionManager) {
|
34
|
+
this.view = view;
|
35
|
+
this.service = service;
|
36
|
+
this.sessionManager = sessionManager;
|
37
|
+
}
|
38
|
+
|
39
|
+
public void start(AcceptsOneWidget display, EventBus eventBus) {
|
40
|
+
display.setWidget(view.asWidget());
|
41
|
+
view.setPresenter(this);
|
42
|
+
}
|
43
|
+
|
44
|
+
public void login(final String login, String password) {
|
45
|
+
Authentication authentication = new Authentication(login, password);
|
46
|
+
service.create(authentication, new MethodCallback<Session<User>>() {
|
47
|
+
|
48
|
+
public void onSuccess(Method method, Session<User> session) {
|
49
|
+
GWT.log("logged in: " + login);
|
50
|
+
sessionManager.login(session);
|
51
|
+
}
|
52
|
+
|
53
|
+
public void onFailure(Method method, Throwable exception) {
|
54
|
+
GWT.log("login failed: " + exception.getMessage(), exception);
|
55
|
+
sessionManager.accessDenied();
|
56
|
+
}
|
57
|
+
});
|
58
|
+
}
|
59
|
+
}
|
@@ -0,0 +1,20 @@
|
|
1
|
+
package <%= places_package %>;
|
2
|
+
|
3
|
+
import <%= base_package %>.ActivityPlace;
|
4
|
+
import <%= managed_package %>.ActivityFactory;
|
5
|
+
|
6
|
+
import com.google.gwt.activity.shared.Activity;
|
7
|
+
|
8
|
+
public class LoginPlace extends ActivityPlace {
|
9
|
+
|
10
|
+
public static final LoginPlace LOGIN = new LoginPlace();
|
11
|
+
|
12
|
+
private LoginPlace() {
|
13
|
+
super(null);
|
14
|
+
}
|
15
|
+
|
16
|
+
@Override
|
17
|
+
public Activity create(ActivityFactory factory) {
|
18
|
+
return factory.create(this);
|
19
|
+
}
|
20
|
+
}
|
@@ -0,0 +1,18 @@
|
|
1
|
+
<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
|
2
|
+
<ui:UiBinder
|
3
|
+
xmlns:ui="urn:ui:com.google.gwt.uibinder"
|
4
|
+
xmlns:g="urn:import:com.google.gwt.user.client.ui">
|
5
|
+
|
6
|
+
<ui:style>
|
7
|
+
</ui:style>
|
8
|
+
|
9
|
+
<g:SimplePanel>
|
10
|
+
<g:VerticalPanel>
|
11
|
+
<g:InlineHTML>username</g:InlineHTML>
|
12
|
+
<g:TextBox ui:field="username"/>
|
13
|
+
<g:InlineHTML>password</g:InlineHTML>
|
14
|
+
<g:PasswordTextBox ui:field="password"/>
|
15
|
+
<g:Button ui:field="loginButton">login</g:Button>
|
16
|
+
</g:VerticalPanel>
|
17
|
+
</g:SimplePanel>
|
18
|
+
</ui:UiBinder>
|
@@ -0,0 +1,21 @@
|
|
1
|
+
package <%= views_package %>;
|
2
|
+
|
3
|
+
import com.google.gwt.core.client.GWT;
|
4
|
+
import com.google.gwt.uibinder.client.UiBinder;
|
5
|
+
import com.google.gwt.uibinder.client.UiTemplate;
|
6
|
+
import com.google.gwt.user.client.ui.Widget;
|
7
|
+
import com.google.inject.Singleton;
|
8
|
+
|
9
|
+
@Singleton
|
10
|
+
public class LoginViewImpl extends <%= gwt_rails_session_package %>.LoginViewImpl {
|
11
|
+
|
12
|
+
@UiTemplate("LoginView.ui.xml")
|
13
|
+
interface LoginViewUiBinder extends UiBinder<Widget,<%= gwt_rails_session_package %>.LoginViewImpl> {}
|
14
|
+
|
15
|
+
private static LoginViewUiBinder uiBinder = GWT.create(LoginViewUiBinder.class);
|
16
|
+
|
17
|
+
public LoginViewImpl() {
|
18
|
+
super(uiBinder);
|
19
|
+
}
|
20
|
+
|
21
|
+
}
|
@@ -1,9 +1,12 @@
|
|
1
1
|
#-*- mode: ruby -*-
|
2
|
-
|
2
|
+
GWT_VERSION = '2.2.0'
|
3
|
+
jar('org.fusesource.restygwt:restygwt', '1.2-SNAPSHOT').scope :provided
|
3
4
|
jar('javax.ws.rs:jsr311-api', '1.1').scope :provided
|
4
|
-
jar('com.google.gwt:gwt-user',
|
5
|
+
jar('com.google.gwt:gwt-user', GWT_VERSION).scope :provided
|
6
|
+
jar('com.google.gwt.inject:gin', '1.5.0').scope :provided
|
7
|
+
jar('de.mkristian.rails-gwt:rails-gwt', '0.1.0-SNAPSHOT').scope :provided
|
5
8
|
|
6
|
-
plugin('org.codehaus.mojo:gwt-maven-plugin',
|
9
|
+
plugin('org.codehaus.mojo:gwt-maven-plugin', GWT_VERSION) do |gwt|
|
7
10
|
gwt.with({ :warSourceDirectory => "${basedir}/public",
|
8
11
|
:webXml => "${basedir}/public/WEB-INF/web.xml",
|
9
12
|
:webappDirectory => "${basedir}/public",
|
@@ -18,7 +21,6 @@ plugin('org.codehaus.mojo:gwt-maven-plugin', '2.2.0') do |gwt|
|
|
18
21
|
})
|
19
22
|
gwt.executions.goals << ["clean", "compile", "test"]
|
20
23
|
end
|
21
|
-
plugin(:bundler, "${jruby.plugins.version}").execution.goals << :install
|
22
24
|
plugin(:rails3).in_phase("initialize").execute_goal(:pom).with :force => true
|
23
25
|
|
24
26
|
#-- Macs need the -d32 -XstartOnFirstThread jvm options -->
|
@@ -27,4 +29,5 @@ profile("mac") do |mac|
|
|
27
29
|
mac.plugin('org.codehaus.mojo:gwt-maven-plugin').with(:extraJvmArgs => "-d32 -XstartOnFirstThread -Xmx512m")
|
28
30
|
end
|
29
31
|
|
32
|
+
repository(:snapshots).url "http://mojo.saumya.de"
|
30
33
|
# vim: syntax=Ruby
|
@@ -0,0 +1,24 @@
|
|
1
|
+
package <%= managed_package %>;
|
2
|
+
|
3
|
+
import <%= gwt_rails_package %>.RestfulAction;
|
4
|
+
|
5
|
+
public abstract class RestfulPlace<T> extends ActivityPlace {
|
6
|
+
|
7
|
+
public final RestfulAction action;
|
8
|
+
|
9
|
+
private T resource;
|
10
|
+
|
11
|
+
public RestfulPlace(RestfulAction restfulAction) {
|
12
|
+
this.action = restfulAction;
|
13
|
+
}
|
14
|
+
|
15
|
+
public void setResource(T resource) {
|
16
|
+
if (this.resource == null) {
|
17
|
+
this.resource = resource;
|
18
|
+
}
|
19
|
+
}
|
20
|
+
|
21
|
+
public T getResource() {
|
22
|
+
return resource;
|
23
|
+
}
|
24
|
+
}
|
@@ -0,0 +1,71 @@
|
|
1
|
+
package <%= base_package %>;
|
2
|
+
|
3
|
+
import javax.inject.Inject;
|
4
|
+
|
5
|
+
import <%= managed_package %>.ActivityFactory;
|
6
|
+
import <%= models_package %>.User;
|
7
|
+
import <%= places_package %>.LoginPlace;
|
8
|
+
|
9
|
+
import com.google.gwt.activity.shared.Activity;
|
10
|
+
import com.google.gwt.place.shared.Place;
|
11
|
+
|
12
|
+
import de.mkristian.gwt.rails.Notice;
|
13
|
+
import de.mkristian.gwt.rails.RestfulPlace;
|
14
|
+
import de.mkristian.gwt.rails.session.NeedsAuthorization;
|
15
|
+
import de.mkristian.gwt.rails.session.NoAuthorization;
|
16
|
+
import de.mkristian.gwt.rails.session.SessionManager;
|
17
|
+
|
18
|
+
public class SessionActivityPlaceActivityMapper extends ActivityPlaceActivityMapper {
|
19
|
+
|
20
|
+
private final SessionManager<User> manager;
|
21
|
+
|
22
|
+
@Inject
|
23
|
+
public SessionActivityPlaceActivityMapper(ActivityFactory factory, SessionManager<User> manager, Notice notice) {
|
24
|
+
super(factory, notice);
|
25
|
+
this.manager = manager;
|
26
|
+
}
|
27
|
+
|
28
|
+
public Activity getActivity(Place place) {
|
29
|
+
return pessimisticGetActivity(place);
|
30
|
+
}
|
31
|
+
|
32
|
+
/**
|
33
|
+
* pessimistic in the sense that default is authorisation, only the places
|
34
|
+
* which implements {@link NoAuthorization} will be omitted by the check.
|
35
|
+
*/
|
36
|
+
protected Activity pessimisticGetActivity(Place place) {
|
37
|
+
if (!(place instanceof NoAuthorization)) {
|
38
|
+
if(manager.isActive()){
|
39
|
+
if(!manager.isAllowed((RestfulPlace)place)){
|
40
|
+
notice.setText("nothing to see");
|
41
|
+
return null;
|
42
|
+
}
|
43
|
+
//TODO move into a dispatch filter or callback filter
|
44
|
+
manager.resetTimer();
|
45
|
+
}
|
46
|
+
else {
|
47
|
+
return LoginPlace.LOGIN.create(factory);
|
48
|
+
}
|
49
|
+
}
|
50
|
+
return super.getActivity(place);
|
51
|
+
}
|
52
|
+
|
53
|
+
/**
|
54
|
+
* optimistic in the sense that default is no authorisation, only the places
|
55
|
+
* which implements {@link NeedsAuthorization} will be checked.
|
56
|
+
*/
|
57
|
+
protected Activity optimisticGetActivity(Place place) {
|
58
|
+
if (place instanceof NeedsAuthorization) {
|
59
|
+
if(manager.isActive()){
|
60
|
+
if(!manager.isAllowed((RestfulPlace)place)){
|
61
|
+
notice.setText("nothing to see");
|
62
|
+
return null;
|
63
|
+
}
|
64
|
+
}
|
65
|
+
else {
|
66
|
+
return LoginPlace.LOGIN.create(factory);
|
67
|
+
}
|
68
|
+
}
|
69
|
+
return super.getActivity(place);
|
70
|
+
}
|
71
|
+
}
|
@@ -0,0 +1,23 @@
|
|
1
|
+
package <%= restservices_package %>;
|
2
|
+
|
3
|
+
import javax.ws.rs.DELETE;
|
4
|
+
import javax.ws.rs.POST;
|
5
|
+
import javax.ws.rs.Path;
|
6
|
+
|
7
|
+
import <%= models_package %>.User;
|
8
|
+
|
9
|
+
import org.fusesource.restygwt.client.MethodCallback;
|
10
|
+
import org.fusesource.restygwt.client.RestService;
|
11
|
+
|
12
|
+
import <%= gwt_rails_session_package %>.Authentication;
|
13
|
+
import <%= gwt_rails_session_package %>.Session;
|
14
|
+
|
15
|
+
@Path("/session")
|
16
|
+
public interface SessionRestService extends RestService {
|
17
|
+
|
18
|
+
@POST
|
19
|
+
void create(Authentication authentication, MethodCallback<Session<User>> callback);
|
20
|
+
|
21
|
+
@DELETE
|
22
|
+
void destroy(MethodCallback<Void> callback);
|
23
|
+
}
|
@@ -0,0 +1,17 @@
|
|
1
|
+
.notice
|
2
|
+
{
|
3
|
+
background-color:lightgoldenRodYellow;
|
4
|
+
border:1px solid darkgoldenrod;
|
5
|
+
color:darkgoldenrod;
|
6
|
+
margin-left:35%;
|
7
|
+
margin-right:35%;
|
8
|
+
padding-left:5%;
|
9
|
+
width:30%;
|
10
|
+
padding-right:5%;
|
11
|
+
padding-top:.5em;
|
12
|
+
padding-bottom:.5em;
|
13
|
+
overflow:auto;;
|
14
|
+
position:absolute;
|
15
|
+
top:3em;
|
16
|
+
opacity:.9;
|
17
|
+
}
|