Able to edit avatar while uploading

This commit is contained in:
Robin Shen 2018-11-04 10:24:16 +08:00
parent 17ab9bd963
commit cbfede1853
30 changed files with 507 additions and 400 deletions

View File

@ -209,6 +209,7 @@ public class OneUrlMapper extends CompoundRequestMapper {
add(new OnePageMapper("projects/${project}/builds", BuildListPage.class));
add(new OnePageMapper("projects/${project}/settings/general", GeneralSettingPage.class));
add(new OnePageMapper("projects/${project}/settings/avatar", io.onedev.server.web.page.project.setting.avatar.AvatarEditPage.class));
add(new OnePageMapper("projects/${project}/settings/teams", TeamListPage.class));
add(new OnePageMapper("projects/${project}/settings/teams/new", NewTeamPage.class));
add(new OnePageMapper("projects/${project}/settings/teams/${team}", TeamEditPage.class));

View File

@ -0,0 +1,25 @@
package io.onedev.server.web.asset.cropper;
import java.util.List;
import org.apache.wicket.markup.head.CssHeaderItem;
import org.apache.wicket.markup.head.HeaderItem;
import org.apache.wicket.request.resource.CssResourceReference;
import org.apache.wicket.request.resource.JavaScriptResourceReference;
public class CropperResourceReference extends JavaScriptResourceReference {
private static final long serialVersionUID = 1L;
public CropperResourceReference() {
super(CropperResourceReference.class, "cropper.min.js");
}
@Override
public List<HeaderItem> getDependencies() {
List<HeaderItem> dependencies = super.getDependencies();
dependencies.add(CssHeaderItem.forReference(new CssResourceReference(CropperResourceReference.class, "cropper.min.css")));
return dependencies;
}
}

View File

@ -0,0 +1,9 @@
/*!
* Cropper.js v1.4.3
* https://fengyuanchen.github.io/cropperjs
*
* Copyright 2015-present Chen Fengyuan
* Released under the MIT license
*
* Date: 2018-10-24T13:07:11.429Z
*/.cropper-container{direction:ltr;font-size:0;line-height:0;position:relative;-ms-touch-action:none;touch-action:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.cropper-container img{display:block;height:100%;image-orientation:0deg;max-height:none!important;max-width:none!important;min-height:0!important;min-width:0!important;width:100%}.cropper-canvas,.cropper-crop-box,.cropper-drag-box,.cropper-modal,.cropper-wrap-box{bottom:0;left:0;position:absolute;right:0;top:0}.cropper-canvas,.cropper-wrap-box{overflow:hidden}.cropper-drag-box{background-color:#fff;opacity:0}.cropper-modal{background-color:#000;opacity:.5}.cropper-view-box{display:block;height:100%;outline-color:rgba(51,153,255,.75);outline:1px solid #39f;overflow:hidden;width:100%}.cropper-dashed{border:0 dashed #eee;display:block;opacity:.5;position:absolute}.cropper-dashed.dashed-h{border-bottom-width:1px;border-top-width:1px;height:33.33333%;left:0;top:33.33333%;width:100%}.cropper-dashed.dashed-v{border-left-width:1px;border-right-width:1px;height:100%;left:33.33333%;top:0;width:33.33333%}.cropper-center{display:block;height:0;left:50%;opacity:.75;position:absolute;top:50%;width:0}.cropper-center:after,.cropper-center:before{background-color:#eee;content:" ";display:block;position:absolute}.cropper-center:before{height:1px;left:-3px;top:0;width:7px}.cropper-center:after{height:7px;left:0;top:-3px;width:1px}.cropper-face,.cropper-line,.cropper-point{display:block;height:100%;opacity:.1;position:absolute;width:100%}.cropper-face{background-color:#fff;left:0;top:0}.cropper-line{background-color:#39f}.cropper-line.line-e{cursor:ew-resize;right:-3px;top:0;width:5px}.cropper-line.line-n{cursor:ns-resize;height:5px;left:0;top:-3px}.cropper-line.line-w{cursor:ew-resize;left:-3px;top:0;width:5px}.cropper-line.line-s{bottom:-3px;cursor:ns-resize;height:5px;left:0}.cropper-point{background-color:#39f;height:5px;opacity:.75;width:5px}.cropper-point.point-e{cursor:ew-resize;margin-top:-3px;right:-3px;top:50%}.cropper-point.point-n{cursor:ns-resize;left:50%;margin-left:-3px;top:-3px}.cropper-point.point-w{cursor:ew-resize;left:-3px;margin-top:-3px;top:50%}.cropper-point.point-s{bottom:-3px;cursor:s-resize;left:50%;margin-left:-3px}.cropper-point.point-ne{cursor:nesw-resize;right:-3px;top:-3px}.cropper-point.point-nw{cursor:nwse-resize;left:-3px;top:-3px}.cropper-point.point-sw{bottom:-3px;cursor:nesw-resize;left:-3px}.cropper-point.point-se{bottom:-3px;cursor:nwse-resize;height:20px;opacity:1;right:-3px;width:20px}@media (min-width:768px){.cropper-point.point-se{height:15px;width:15px}}@media (min-width:992px){.cropper-point.point-se{height:10px;width:10px}}@media (min-width:1200px){.cropper-point.point-se{height:5px;opacity:.75;width:5px}}.cropper-point.point-se:before{background-color:#39f;bottom:-50%;content:" ";display:block;height:200%;opacity:0;position:absolute;right:-50%;width:200%}.cropper-invisible{opacity:0}.cropper-bg{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA3NCSVQICAjb4U/gAAAABlBMVEXMzMz////TjRV2AAAACXBIWXMAAArrAAAK6wGCiw1aAAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M26LyyjAAAABFJREFUCJlj+M/AgBVhF/0PAH6/D/HkDxOGAAAAAElFTkSuQmCC")}.cropper-hide{display:block;height:0;position:absolute;width:0}.cropper-hidden{display:none!important}.cropper-move{cursor:move}.cropper-crop{cursor:crosshair}.cropper-disabled .cropper-drag-box,.cropper-disabled .cropper-face,.cropper-disabled .cropper-line,.cropper-disabled .cropper-point{cursor:not-allowed}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,13 @@
package io.onedev.server.web.component.avatarupload;
import org.apache.wicket.ajax.AjaxRequestTarget;
import io.onedev.server.web.util.AjaxPayload;
public class AvatarFileSelected extends AjaxPayload {
public AvatarFileSelected(AjaxRequestTarget target) {
super(target);
}
}

View File

@ -0,0 +1,11 @@
<wicket:panel>
<div class="avatar-upload">
<input type="text" wicket:id="data">
<input wicket:id="fileInput" type="file" accept="image/*">
<label wicket:id="fileLabel"><i class="fa fa-file-image-o"></i> Select file...</label>
<div class="preview clearfix">
<div class="cropping pull-left"><img></div>
<div class="cropped pull-left"><img></div>
</div>
</div>
</wicket:panel>

View File

@ -0,0 +1,116 @@
package io.onedev.server.web.component.avatarupload;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import javax.annotation.Nullable;
import javax.imageio.ImageIO;
import javax.xml.bind.DatatypeConverter;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.event.Broadcast;
import org.apache.wicket.markup.ComponentTag;
import org.apache.wicket.markup.head.IHeaderResponse;
import org.apache.wicket.markup.head.JavaScriptHeaderItem;
import org.apache.wicket.markup.head.OnDomReadyHeaderItem;
import org.apache.wicket.markup.html.WebComponent;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.form.FormComponentPanel;
import org.apache.wicket.markup.html.form.TextField;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.Model;
import io.onedev.server.web.behavior.AbstractPostAjaxBehavior;
import io.onedev.utils.FileUtils;
import io.onedev.utils.StringUtils;
@SuppressWarnings("serial")
public class AvatarUploadField extends FormComponentPanel<String> {
private TextField<String> dataField;
private AbstractPostAjaxBehavior behavior;
public AvatarUploadField(String id, IModel<String> model) {
super(id, model);
}
@Override
protected void onInitialize() {
super.onInitialize();
add(dataField = new TextField<String>("data", Model.of(getModelObject())));
WebComponent fileInput = new WebComponent("fileInput");
fileInput.setOutputMarkupId(true);
add(fileInput);
add(new WebMarkupContainer("fileLabel") {
@Override
protected void onComponentTag(ComponentTag tag) {
super.onComponentTag(tag);
tag.put("for", fileInput.getMarkupId());
}
});
add(behavior = new AbstractPostAjaxBehavior() {
@Override
protected void respond(AjaxRequestTarget target) {
send(AvatarUploadField.this, Broadcast.BUBBLE, new AvatarFileSelected(target));
}
});
}
@Override
public void convertInput() {
setConvertedInput(dataField.getConvertedInput());
}
public static void writeToFile(File file, @Nullable String avatarData) {
if (avatarData != null) {
byte[] imageBytes = DatatypeConverter.parseBase64Binary(StringUtils.substringAfter(avatarData, ","));
try {
BufferedImage image = ImageIO.read(new ByteArrayInputStream(imageBytes));
ImageIO.write(image, "jpeg", file);
} catch (IOException e) {
throw new RuntimeException(e);
}
} else if (file.exists()) {
FileUtils.deleteFile(file);
}
}
@Nullable
public static String readFromFile(File file) {
if (file.exists()) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
ImageIO.write(ImageIO.read(file), "jpeg", baos);
} catch (IOException e) {
throw new RuntimeException(e);
}
return "data:image/jpeg;base64," + DatatypeConverter.printBase64Binary(baos.toByteArray());
} else {
return null;
}
}
@Override
public void renderHead(IHeaderResponse response) {
super.renderHead(response);
response.render(JavaScriptHeaderItem.forReference(new AvatarUploadResourceReference()));
String script = String.format("onedev.server.avatarUpload.onDomReady('%s', %s);",
getMarkupId(), behavior.getCallbackFunction());
response.render(OnDomReadyHeaderItem.forScript(script));
}
}

View File

@ -0,0 +1,30 @@
package io.onedev.server.web.component.avatarupload;
import java.util.List;
import org.apache.wicket.markup.head.CssHeaderItem;
import org.apache.wicket.markup.head.HeaderItem;
import org.apache.wicket.markup.head.JavaScriptHeaderItem;
import io.onedev.server.web.asset.cropper.CropperResourceReference;
import io.onedev.server.web.page.base.BaseDependentCssResourceReference;
import io.onedev.server.web.page.base.BaseDependentResourceReference;
public class AvatarUploadResourceReference extends BaseDependentResourceReference {
private static final long serialVersionUID = 1L;
public AvatarUploadResourceReference() {
super(AvatarUploadResourceReference.class, "avatar-upload.js");
}
@Override
public List<HeaderItem> getDependencies() {
List<HeaderItem> dependencies = super.getDependencies();
dependencies.add(JavaScriptHeaderItem.forReference(new CropperResourceReference()));
dependencies.add(CssHeaderItem.forReference(new BaseDependentCssResourceReference(
AvatarUploadResourceReference.class, "avatar-upload.css")));
return dependencies;
}
}

View File

@ -0,0 +1,44 @@
.avatar-upload .preview {
margin-top: 10px;
}
.avatar-upload .cropping {
width: 240px;
height: 240px;
}
.avatar-upload .cropping img {
width: 1px;
height: 1px;
}
.avatar-upload .cropped {
width: 240px;
height: 240px;
margin-left: 20px;
border-radius: 1000px;
overflow: hidden;
}
.avatar-upload input[type="text"] {
display: none;
}
.avatar-upload input[type="file"] {
width: 0.1px;
height: 0.1px;
opacity: 0;
overflow: hidden;
position: absolute;
z-index: -1;
}
.avatar-upload input[type="file"] + label {
font-weight: normal;
border: 1px solid #CECECE;
border-radius: 4px;
display: inline-block;
cursor: pointer;
padding: 6px 12px;
}
.avatar-upload input[type="file"]:focus + label {
outline: 1px dotted blue;
outline: -webkit-focus-ring-color auto 5px;
}
.avatar-upload.no-avatar .preview {
display: none;
}

View File

@ -0,0 +1,50 @@
onedev.server.avatarUpload = {
onDomReady: function(containerId, callback) {
var $container = $("#" + containerId + ">.avatar-upload");
var $data = $container.find("input[type=text]")
var $image = $container.find(".cropping img");
var $file = $container.find("input[type=file]");
if ($data.val()) {
$image.attr("src", $data.val());
} else {
$container.addClass("no-avatar");
}
function preview() {
if ($image[0].hasAttribute("src")) {
if ($image[0].cropper) {
$image[0].cropper.replace($image.attr("src"));
} else {
new Cropper($image[0], {
aspectRatio: 1,
autoCropArea: 1,
crop: function(event) {
var $cropped = $container.find(".cropped");
var data = this.cropper.getCroppedCanvas({
width: $cropped.width(),
height: $cropped.height()
}).toDataURL("image/jpeg");
$cropped.find("img").attr("src", data);
$data.val(data);
}
});
}
}
}
preview();
$file.change(function() {
if ($file[0].files && $file[0].files.length != 0) {
var file = $file[0].files[0];
var reader = new FileReader();
reader.readAsDataURL(file);
reader.addEventListener("load", function () {
callback();
$container.removeClass("no-avatar");
$image.attr("src", reader.result);
preview();
}, false);
}
});
}
}

View File

@ -1,6 +1,5 @@
package io.onedev.server.web.component.project.avatar;
import org.apache.wicket.event.IEvent;
import org.apache.wicket.markup.ComponentTag;
import org.apache.wicket.markup.head.CssHeaderItem;
import org.apache.wicket.markup.head.IHeaderResponse;
@ -13,30 +12,16 @@ import io.onedev.server.web.util.avatar.AvatarManager;
@SuppressWarnings("serial")
public class ProjectAvatar extends WebComponent {
private final Long projectId;
private String url;
public ProjectAvatar(String id, Project project) {
super(id);
AvatarManager avatarManager = OneDev.getInstance(AvatarManager.class);
projectId = project.getId();
url = avatarManager.getAvatarUrl(project.getFacade());
url = getAvatarManager().getAvatarUrl(project.getFacade());
}
@Override
public void onEvent(IEvent<?> event) {
super.onEvent(event);
if (event.getPayload() instanceof ProjectAvatarChanged) {
ProjectAvatarChanged avatarChanged = (ProjectAvatarChanged) event.getPayload();
if (avatarChanged.getProject().getId().equals(projectId)) {
AvatarManager avatarManager = OneDev.getInstance(AvatarManager.class);
url = avatarManager.getAvatarUrl(avatarChanged.getProject().getFacade());
avatarChanged.getHandler().add(this);
}
}
private AvatarManager getAvatarManager() {
return OneDev.getInstance(AvatarManager.class);
}
@Override

View File

@ -1,21 +0,0 @@
package io.onedev.server.web.component.project.avatar;
import org.apache.wicket.ajax.AjaxRequestTarget;
import io.onedev.server.model.Project;
import io.onedev.server.web.util.AjaxPayload;
public class ProjectAvatarChanged extends AjaxPayload {
private final Project project;
public ProjectAvatarChanged(AjaxRequestTarget target, Project project) {
super(target);
this.project = project;
}
public Project getProject() {
return project;
}
}

View File

@ -1,6 +1,5 @@
package io.onedev.server.web.component.project.avatar;
import org.apache.wicket.event.IEvent;
import org.apache.wicket.markup.ComponentTag;
import org.apache.wicket.markup.head.CssHeaderItem;
import org.apache.wicket.markup.head.IHeaderResponse;
@ -18,8 +17,6 @@ import io.onedev.server.web.util.avatar.AvatarManager;
@SuppressWarnings("serial")
public class ProjectAvatarLink extends ViewStateAwarePageLink<Void> {
private final Long projectId;
private final PageParameters params;
private String url;
@ -30,7 +27,6 @@ public class ProjectAvatarLink extends ViewStateAwarePageLink<Void> {
super(id, ProjectBlobPage.class);
AvatarManager avatarManager = OneDev.getInstance(AvatarManager.class);
projectId = project.getId();
params = ProjectPage.paramsOf(project);
url = avatarManager.getAvatarUrl(project.getFacade());
tooltip = project.getName();
@ -46,20 +42,6 @@ public class ProjectAvatarLink extends ViewStateAwarePageLink<Void> {
return Model.of("<img src='" + url + "' class='project-avatar'></img>");
}
@Override
public void onEvent(IEvent<?> event) {
super.onEvent(event);
if (event.getPayload() instanceof ProjectAvatarChanged) {
ProjectAvatarChanged avatarChanged = (ProjectAvatarChanged) event.getPayload();
if (avatarChanged.getProject().getId().equals(projectId)) {
AvatarManager avatarManager = OneDev.getInstance(AvatarManager.class);
url = avatarManager.getAvatarUrl(avatarChanged.getProject().getFacade());
avatarChanged.getHandler().add(this);
}
}
}
@Override
protected void onComponentTag(ComponentTag tag) {
super.onComponentTag(tag);

View File

@ -2,7 +2,6 @@ package io.onedev.server.web.component.user.avatar;
import javax.annotation.Nullable;
import org.apache.wicket.event.IEvent;
import org.apache.wicket.markup.ComponentTag;
import org.apache.wicket.markup.head.CssHeaderItem;
import org.apache.wicket.markup.head.IHeaderResponse;
@ -17,51 +16,27 @@ import io.onedev.server.web.util.avatar.AvatarManager;
@SuppressWarnings("serial")
public class UserAvatar extends WebComponent {
private final Long userId;
private String url;
public UserAvatar(String id, @Nullable User user) {
super(id);
AvatarManager avatarManager = OneDev.getInstance(AvatarManager.class);
if (user == null) {
userId = null;
} else if (user.getId() == null) {
userId = null;
} else {
userId = user.getId();
}
url = avatarManager.getAvatarUrl(user!=null?user.getFacade():null);
url = getAvatarManager().getAvatarUrl(user!=null?user.getFacade():null);
}
public UserAvatar(String id, PersonIdent person) {
super(id);
AvatarManager avatarManager = OneDev.getInstance(AvatarManager.class);
User user = OneDev.getInstance(UserManager.class).find(person);
if (user != null) {
userId = user.getId();
url = avatarManager.getAvatarUrl(user.getFacade());
url = getAvatarManager().getAvatarUrl(user.getFacade());
} else {
userId = null;
url = avatarManager.getAvatarUrl(person);
url = getAvatarManager().getAvatarUrl(person);
}
}
@Override
public void onEvent(IEvent<?> event) {
super.onEvent(event);
if (event.getPayload() instanceof UserAvatarChanged) {
UserAvatarChanged avatarChanged = (UserAvatarChanged) event.getPayload();
if (avatarChanged.getUser().getId().equals(userId)) {
AvatarManager avatarManager = OneDev.getInstance(AvatarManager.class);
url = avatarManager.getAvatarUrl(avatarChanged.getUser().getFacade());
avatarChanged.getHandler().add(this);
}
}
private AvatarManager getAvatarManager() {
return OneDev.getInstance(AvatarManager.class);
}
@Override

View File

@ -1,22 +0,0 @@
package io.onedev.server.web.component.user.avatar;
import org.apache.wicket.ajax.AjaxRequestTarget;
import io.onedev.server.model.User;
import io.onedev.server.web.util.AjaxPayload;
public class UserAvatarChanged extends AjaxPayload {
private final User user;
public UserAvatarChanged(AjaxRequestTarget target, User user) {
super(target);
this.user = user;
}
public User getUser() {
return user;
}
}

View File

@ -3,7 +3,6 @@ package io.onedev.server.web.component.user.avatar;
import javax.annotation.Nullable;
import org.apache.wicket.behavior.AttributeAppender;
import org.apache.wicket.event.IEvent;
import org.apache.wicket.markup.ComponentTag;
import org.apache.wicket.markup.head.CssHeaderItem;
import org.apache.wicket.markup.head.IHeaderResponse;
@ -23,8 +22,6 @@ import io.onedev.server.web.util.avatar.AvatarManager;
@SuppressWarnings("serial")
public class UserAvatarLink extends ViewStateAwarePageLink<Void> {
private final Long userId;
private final PageParameters params;
private final String tooltip;
@ -40,13 +37,10 @@ public class UserAvatarLink extends ViewStateAwarePageLink<Void> {
AvatarManager avatarManager = OneDev.getInstance(AvatarManager.class);
if (user == null) {
userId = null;
params = new PageParameters();
} else if (user.getId() == null) {
userId = null;
params = new PageParameters();
} else {
userId = user.getId();
params = UserPage.paramsOf(user);
}
this.tooltip = tooltip;
@ -64,11 +58,9 @@ public class UserAvatarLink extends ViewStateAwarePageLink<Void> {
User user = OneDev.getInstance(UserManager.class).find(person);
if (user != null) {
userId = user.getId();
params = UserPage.paramsOf(user);
url = avatarManager.getAvatarUrl(user.getFacade());
} else {
userId = null;
params = new PageParameters();
url = avatarManager.getAvatarUrl(person);
}
@ -86,20 +78,6 @@ public class UserAvatarLink extends ViewStateAwarePageLink<Void> {
return Model.of("<img src='" + url + "' class='avatar'></img>");
}
@Override
public void onEvent(IEvent<?> event) {
super.onEvent(event);
if (event.getPayload() instanceof UserAvatarChanged) {
UserAvatarChanged avatarChanged = (UserAvatarChanged) event.getPayload();
if (avatarChanged.getUser().getId().equals(userId)) {
AvatarManager avatarManager = OneDev.getInstance(AvatarManager.class);
url = avatarManager.getAvatarUrl(avatarChanged.getUser().getFacade());
avatarChanged.getHandler().add(this);
}
}
}
@Override
protected void onComponentTag(ComponentTag tag) {
super.onComponentTag(tag);

View File

@ -14,7 +14,7 @@
</a>
</div>
<div class="collapse navbar-collapse" id="navbar-collapse">
<ul class="nav navbar-nav">
<ul class="nav navbar-nav tabs">
<li wicket:id="navProjects"><a wicket:id="link">Projects</a></li>
<li wicket:id="navContributions"><a wicket:id="link"><span wicket:id="label"></span></a></li>
<li wicket:id="navUsers"><a wicket:id="link">Users</a></li>

View File

@ -7,7 +7,7 @@
height: 24px;
margin-left: -6px;
}
.navbar a {
.navbar .tabs a {
font-size: 16px;
}
.navbar-nav>li>a.avatar {

View File

@ -1,16 +1,16 @@
<wicket:extend>
<div class="avatar-edit">
<img wicket:id="avatar"></img>
<form wicket:id="form">
<div wicket:id="feedback"></div>
<div wicket:id="file"></div>
<div class="actions">
<button wicket:id="useUploaded" class="btn btn-primary dirty-aware">Use Uploaded</button>
<a wicket:id="useDefault" class="btn btn-default">Use Default</a>
</div>
</form>
<div class="help-block">
The image size should be less than <span wicket:id="maxSize"></span>M bytes.
<div class="current">
<div class="title">Current avatar</div>
<img wicket:id="avatar"/>
<a wicket:id="useDefault" class="btn btn-default"><i class="fa fa-undo"/> Use Default</a>
</div>
<div class="use-uploaded">
<div class="title">Upload avatar</div>
<form wicket:id="form">
<div wicket:id="avatar"></div>
<button wicket:id="upload" type="submit" class="btn btn-default"><i class="fa fa-upload"></i> Upload</button>
</form>
</div>
</div>
</wicket:extend>

View File

@ -1,39 +1,31 @@
package io.onedev.server.web.page.project.setting.avatar;
import java.util.Collection;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.markup.html.AjaxLink;
import org.apache.wicket.ajax.markup.html.form.AjaxButton;
import org.apache.wicket.event.Broadcast;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.event.IEvent;
import org.apache.wicket.markup.html.form.Button;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.form.upload.FileUpload;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.Model;
import org.apache.wicket.markup.html.link.Link;
import org.apache.wicket.model.PropertyModel;
import org.apache.wicket.request.mapper.parameter.PageParameters;
import org.apache.wicket.util.lang.Bytes;
import de.agilecoders.wicket.core.markup.html.bootstrap.common.NotificationPanel;
import io.onedev.server.OneDev;
import io.onedev.server.security.SecurityUtils;
import io.onedev.server.web.component.dropzonefield.DropzoneField;
import io.onedev.server.web.component.avatarupload.AvatarFileSelected;
import io.onedev.server.web.component.avatarupload.AvatarUploadField;
import io.onedev.server.web.component.project.avatar.ProjectAvatar;
import io.onedev.server.web.component.project.avatar.ProjectAvatarChanged;
import io.onedev.server.web.page.project.setting.ProjectSettingPage;
import io.onedev.server.web.util.avatar.AvatarManager;
@SuppressWarnings("serial")
public class AvatarEditPage extends ProjectSettingPage {
private static final int MAX_IMAGE_SIZE = 5;
private Collection<FileUpload> uploads;
private String uploadedAvatarData;
public AvatarEditPage(PageParameters params) {
super(params);
}
private AvatarManager getAvatarManager() {
return OneDev.getInstance(AvatarManager.class);
}
@Override
protected void onInitialize() {
@ -41,68 +33,51 @@ public class AvatarEditPage extends ProjectSettingPage {
add(new ProjectAvatar("avatar", getProject()));
Form<?> form = new Form<Void>("form");
add(form);
form.add(new NotificationPanel("feedback", form));
form.setMaxSize(Bytes.megabytes(MAX_IMAGE_SIZE));
form.setOutputMarkupId(true);
form.setMultiPart(true);
IModel<Collection<FileUpload>> model = new PropertyModel<>(this, "uploads");
DropzoneField dropzoneField = new DropzoneField("file", model, "image/*", 1, MAX_IMAGE_SIZE);
dropzoneField.setRequired(true).setLabel(Model.of("Image"));
form.add(dropzoneField);
form.add(new AjaxButton("useUploaded") {
@Override
protected void onError(AjaxRequestTarget target, Form<?> form) {
super.onError(target, form);
target.add(form);
}
@Override
protected void onSubmit(AjaxRequestTarget target, Form<?> form) {
super.onSubmit(target, form);
FileUpload upload = uploads.iterator().next();
AvatarManager avatarManager = OneDev.getInstance(AvatarManager.class);
avatarManager.useAvatar(projectModel.getObject().getFacade(), upload);
send(getPage(), Broadcast.BREADTH, new ProjectAvatarChanged(target, projectModel.getObject()));
target.add(form);
}
});
form.add(new AjaxLink<Void>("useDefault") {
add(new Link<Void>("useDefault") {
@Override
protected void onConfigure() {
super.onConfigure();
AvatarManager avatarManager = OneDev.getInstance(AvatarManager.class);
setVisible(avatarManager.getUploaded(projectModel.getObject().getFacade()).exists());
setVisible(getAvatarManager().getUploaded(getProject().getFacade()).exists());
}
@Override
public void onClick(AjaxRequestTarget target) {
AvatarManager avatarManager = OneDev.getInstance(AvatarManager.class);
avatarManager.useAvatar(projectModel.getObject().getFacade(), null);
target.add(form);
send(getPage(), Broadcast.BREADTH, new ProjectAvatarChanged(target, projectModel.getObject()));
public void onClick() {
getAvatarManager().useAvatar(getProject().getFacade(), null);
setResponsePage(AvatarEditPage.class, AvatarEditPage.paramsOf(getProject()));
}
});
Button uploadButton = new Button("upload");
uploadButton.setVisible(false).setOutputMarkupPlaceholderTag(true);
add(new Label("maxSize", MAX_IMAGE_SIZE));
Form<?> form = new Form<Void>("form") {
@Override
protected void onSubmit() {
super.onSubmit();
AvatarManager avatarManager = OneDev.getInstance(AvatarManager.class);
avatarManager.useAvatar(getProject().getFacade(), uploadedAvatarData);
setResponsePage(AvatarEditPage.class, AvatarEditPage.paramsOf(getProject()));
}
@Override
public void onEvent(IEvent<?> event) {
super.onEvent(event);
if (event.getPayload() instanceof AvatarFileSelected) {
AvatarFileSelected avatarFileSelected = (AvatarFileSelected) event.getPayload();
uploadButton.setVisible(true);
avatarFileSelected.getHandler().add(uploadButton);
}
}
};
PropertyModel<String> avatarDataModel = new PropertyModel<String>(this, "uploadedAvatarData");
form.add(new AvatarUploadField("avatar", avatarDataModel));
form.add(uploadButton);
setOutputMarkupId(true);
}
@Override
protected boolean isPermitted() {
return SecurityUtils.canAdministrate(getProject().getFacade());
add(form);
}
}

View File

@ -66,12 +66,24 @@
#project-setting .authorization.list .head .select2-container {
width: 240px;
}
#project-setting .avatar-edit img.project-avatar {
height: 240px;
width: 240px;
#project-setting .avatar-edit .title {
font-size: 18px;
font-weight: bold;
margin-bottom: 20px;
}
#project-setting .avatar-edit .upload {
margin-right: 8px;
#project-setting .avatar-edit>.current {
margin-bottom: 20px;
padding-bottom: 20px;
border-bottom: 1px solid #E8E8E8;
}
#project-setting .avatar-edit .current img {
height: 240px;
width: 240px;
display: block;
}
#project-setting .avatar-edit .current a {
margin-top: 20px;
}
#project-setting .avatar-edit form>button {
margin-top: 20px;
}

View File

@ -1,3 +1,6 @@
<wicket:extend>
<a wicket:id="test">test</a>
<form wicket:id="form">
<div wicket:id="avatar" style="margin-bottom: 20px;"></div>
<input type="submit" class="form-control" value="Save">
</form>
</wicket:extend>

View File

@ -1,84 +1,39 @@
package io.onedev.server.web.page.test;
import java.io.File;
import org.apache.wicket.markup.head.IHeaderResponse;
import org.apache.wicket.markup.head.JavaScriptHeaderItem;
import org.apache.wicket.markup.head.OnDomReadyHeaderItem;
import org.apache.wicket.markup.html.link.Link;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.model.Model;
import io.onedev.server.web.component.avatarupload.AvatarUploadField;
import io.onedev.server.web.page.base.BasePage;
@SuppressWarnings("serial")
public class TestPage extends BasePage {
private AvatarUploadField avatarUpload;
@Override
protected void onInitialize() {
super.onInitialize();
add(new Link<Void>("test") {
String avatarData = AvatarUploadField.readFromFile(new File("w:\\temp\\avatar.jpg"));
Form<?> form = new Form<Void>("form") {
@Override
public void onClick() {
/*
for (int k=0; k<100; k++) {
UnitOfWork unitOfWork = OneDev.getInstance(UnitOfWork.class);
unitOfWork.begin();
Session session = unitOfWork.getSession();
session.beginTransaction();
Project project = OneDev.getInstance(ProjectManager.class).load(1L);
User user = OneDev.getInstance(UserManager.class).load(1L);
Milestone milestone = OneDev.getInstance(MilestoneManager.class).load(1L);
try {
for (int i=0; i<10000; i++) {
long j = k*10000 +i;
Issue issue = new Issue();
issue.setProject(project);
issue.setTitle("Issue " + j);
issue.setNumber(j+2);
if (j<10000)
issue.setState("Open");
else
issue.setState("Closed");
if (j<1000)
issue.setMilestone(milestone);
issue.setSubmitDate(new Date());
issue.setSubmitter(user);
issue.setUUID(UUID.randomUUID().toString());
LastActivity lastActivity = new LastActivity();
lastActivity.setDescription("submitted");
lastActivity.setUser(issue.getSubmitter());
lastActivity.setDate(issue.getSubmitDate());
issue.setLastActivity(lastActivity);
session.save(issue);
IssueFieldUnary unary = new IssueFieldUnary();
unary.setIssue(issue);
unary.setName("Type");
unary.setType(InputSpec.ENUMERATION);
if (i%4 == 0) {
unary.setOrdinal(1L);
unary.setValue("Bug");
} else if (i%4 == 1) {
unary.setOrdinal(2L);
unary.setValue("Task");
} else if (i%4 == 2) {
unary.setOrdinal(3L);
unary.setValue("New Feature");
} else {
unary.setOrdinal(4L);
unary.setValue("Improvement");
}
session.save(unary);
}
session.flush();
session.getTransaction().commit();
} finally {
unitOfWork.end();
}
System.out.println(k);
}
*/
protected void onSubmit() {
super.onSubmit();
AvatarUploadField.writeToFile(new File("w:\\temp\\avatar.jpg"), avatarUpload.getModelObject());
}
});
};
form.add(avatarUpload = new AvatarUploadField("avatar", Model.of(avatarData)));
avatarUpload.setRequired(true);
add(form);
}
@Override

View File

@ -6,7 +6,7 @@ import org.apache.wicket.markup.head.CssHeaderItem;
import org.apache.wicket.markup.head.HeaderItem;
import org.apache.wicket.markup.head.JavaScriptHeaderItem;
import io.onedev.server.web.asset.jqueryui.JQueryUIResourceReference;
import io.onedev.server.web.asset.cropper.CropperResourceReference;
import io.onedev.server.web.page.base.BaseDependentCssResourceReference;
import io.onedev.server.web.page.base.BaseDependentResourceReference;
@ -21,7 +21,7 @@ public class TestResourceReference extends BaseDependentResourceReference {
@Override
public List<HeaderItem> getDependencies() {
List<HeaderItem> dependencies = super.getDependencies();
dependencies.add(JavaScriptHeaderItem.forReference(new JQueryUIResourceReference()));
dependencies.add(JavaScriptHeaderItem.forReference(new CropperResourceReference()));
dependencies.add(CssHeaderItem.forReference(new BaseDependentCssResourceReference(
TestResourceReference.class, "test.css")));
return dependencies;

View File

@ -1,16 +1,16 @@
<wicket:extend>
<div class="avatar-edit">
<img wicket:id="avatar"></img>
<form wicket:id="form">
<div wicket:id="feedback"></div>
<div wicket:id="file"></div>
<div class="actions">
<button wicket:id="useUploaded" class="btn btn-primary dirty-aware">Use Uploaded</button>
<a wicket:id="useDefault" class="btn btn-default">Use Default</a>
</div>
</form>
<div class="help-block">
The image size should be less than <span wicket:id="maxSize"></span>M bytes.
<div class="current">
<div class="title">Current avatar</div>
<img wicket:id="avatar"/>
<a wicket:id="useDefault" class="btn btn-default"><i class="fa fa-undo"/> Use Default</a>
</div>
<div class="use-uploaded">
<div class="title">Upload avatar</div>
<form wicket:id="form">
<div wicket:id="avatar"></div>
<button wicket:id="upload" type="submit" class="btn btn-default"><i class="fa fa-upload"></i> Upload</button>
</form>
</div>
</div>
</wicket:extend>

View File

@ -1,38 +1,31 @@
package io.onedev.server.web.page.user;
import java.util.Collection;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.markup.html.AjaxLink;
import org.apache.wicket.ajax.markup.html.form.AjaxButton;
import org.apache.wicket.event.Broadcast;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.event.IEvent;
import org.apache.wicket.markup.html.form.Button;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.form.upload.FileUpload;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.Model;
import org.apache.wicket.markup.html.link.Link;
import org.apache.wicket.model.PropertyModel;
import org.apache.wicket.request.mapper.parameter.PageParameters;
import org.apache.wicket.util.lang.Bytes;
import de.agilecoders.wicket.core.markup.html.bootstrap.common.NotificationPanel;
import io.onedev.server.OneDev;
import io.onedev.server.security.SecurityUtils;
import io.onedev.server.web.component.dropzonefield.DropzoneField;
import io.onedev.server.web.component.avatarupload.AvatarFileSelected;
import io.onedev.server.web.component.avatarupload.AvatarUploadField;
import io.onedev.server.web.component.user.avatar.UserAvatar;
import io.onedev.server.web.component.user.avatar.UserAvatarChanged;
import io.onedev.server.web.util.avatar.AvatarManager;
@SuppressWarnings("serial")
public class AvatarEditPage extends UserPage {
private static final int MAX_IMAGE_SIZE = 5;
private Collection<FileUpload> uploads;
private String uploadedAvatarData;
public AvatarEditPage(PageParameters params) {
super(params);
}
private AvatarManager getAvatarManager() {
return OneDev.getInstance(AvatarManager.class);
}
@Override
protected void onInitialize() {
@ -40,63 +33,51 @@ public class AvatarEditPage extends UserPage {
add(new UserAvatar("avatar", getUser()));
Form<?> form = new Form<Void>("form");
add(form);
form.add(new NotificationPanel("feedback", form));
form.setMaxSize(Bytes.megabytes(MAX_IMAGE_SIZE));
form.setOutputMarkupId(true);
form.setMultiPart(true);
IModel<Collection<FileUpload>> model = new PropertyModel<>(this, "uploads");
DropzoneField dropzoneField = new DropzoneField("file", model, "image/*", 1, MAX_IMAGE_SIZE);
dropzoneField.setRequired(true).setLabel(Model.of("Image"));
form.add(dropzoneField);
form.add(new AjaxButton("useUploaded") {
@Override
protected void onError(AjaxRequestTarget target, Form<?> form) {
super.onError(target, form);
target.add(form);
}
@Override
protected void onSubmit(AjaxRequestTarget target, Form<?> form) {
super.onSubmit(target, form);
FileUpload upload = uploads.iterator().next();
AvatarManager avatarManager = OneDev.getInstance(AvatarManager.class);
avatarManager.useAvatar(userModel.getObject().getFacade(), upload);
send(getPage(), Broadcast.BREADTH, new UserAvatarChanged(target, userModel.getObject()));
target.add(form);
}
});
form.add(new AjaxLink<Void>("useDefault") {
add(new Link<Void>("useDefault") {
@Override
protected void onConfigure() {
super.onConfigure();
AvatarManager avatarManager = OneDev.getInstance(AvatarManager.class);
setVisible(avatarManager.getUploaded(userModel.getObject().getFacade()).exists());
setVisible(getAvatarManager().getUploaded(getUser().getFacade()).exists());
}
@Override
public void onClick(AjaxRequestTarget target) {
AvatarManager avatarManager = OneDev.getInstance(AvatarManager.class);
avatarManager.useAvatar(userModel.getObject().getFacade(), null);
target.add(form);
send(getPage(), Broadcast.BREADTH, new UserAvatarChanged(target, userModel.getObject()));
public void onClick() {
getAvatarManager().useAvatar(getUser().getFacade(), null);
setResponsePage(AvatarEditPage.class, AvatarEditPage.paramsOf(getUser()));
}
});
Button uploadButton = new Button("upload");
uploadButton.setVisible(false).setOutputMarkupPlaceholderTag(true);
add(new Label("maxSize", MAX_IMAGE_SIZE));
Form<?> form = new Form<Void>("form") {
@Override
protected void onSubmit() {
super.onSubmit();
AvatarManager avatarManager = OneDev.getInstance(AvatarManager.class);
avatarManager.useAvatar(getUser().getFacade(), uploadedAvatarData);
setResponsePage(AvatarEditPage.class, AvatarEditPage.paramsOf(getUser()));
}
@Override
public void onEvent(IEvent<?> event) {
super.onEvent(event);
if (event.getPayload() instanceof AvatarFileSelected) {
AvatarFileSelected avatarFileSelected = (AvatarFileSelected) event.getPayload();
uploadButton.setVisible(true);
avatarFileSelected.getHandler().add(uploadButton);
}
}
};
PropertyModel<String> avatarDataModel = new PropertyModel<String>(this, "uploadedAvatarData");
form.add(new AvatarUploadField("avatar", avatarDataModel));
form.add(uploadButton);
setOutputMarkupId(true);
add(form);
}
@Override

View File

@ -18,15 +18,6 @@
display: none;
}
#user .avatar-edit img.avatar {
height: 240px;
width: 240px;
margin-bottom: 20px;
}
#user .avatar-edit .upload {
margin-right: 8px;
}
#user .project.list .head .select2-container {
width: 240px;
}
@ -35,6 +26,28 @@
margin-left: 4px;
}
#user .avatar-edit .title {
margin-bottom: 20px;
font-size: 18px;
font-weight: bold;
}
#user .avatar-edit>.current {
margin-bottom: 20px;
padding-bottom: 20px;
border-bottom: 1px solid #E8E8E8;
}
#user .avatar-edit .current img {
height: 240px;
width: 240px;
display: block;
}
#user .avatar-edit .current a {
margin-top: 20px;
}
#user .avatar-edit form>button {
margin-top: 20px;
}
.user.list .actions a {
color: #666;
}
@ -48,4 +61,4 @@
.token-generate .token-value a {
color: #444;
margin-left: 4px;
}
}

View File

@ -4,7 +4,6 @@ import java.io.File;
import javax.annotation.Nullable;
import org.apache.wicket.markup.html.form.upload.FileUpload;
import org.eclipse.jgit.lib.PersonIdent;
import io.onedev.server.util.facade.ProjectFacade;
@ -34,9 +33,9 @@ public interface AvatarManager {
*/
String getAvatarUrl(PersonIdent person);
void useAvatar(UserFacade user, @Nullable FileUpload upload);
void useAvatar(UserFacade user, @Nullable String avatarData);
void useAvatar(ProjectFacade project, @Nullable FileUpload upload);
void useAvatar(ProjectFacade project, @Nullable String avatarData);
File getUploaded(UserFacade user);

View File

@ -12,7 +12,6 @@ import javax.inject.Inject;
import javax.inject.Singleton;
import org.apache.commons.codec.binary.Hex;
import org.apache.wicket.markup.html.form.upload.FileUpload;
import org.eclipse.jgit.lib.PersonIdent;
import com.google.common.base.Splitter;
@ -24,7 +23,7 @@ import io.onedev.server.manager.SettingManager;
import io.onedev.server.persistence.annotation.Sessional;
import io.onedev.server.util.facade.ProjectFacade;
import io.onedev.server.util.facade.UserFacade;
import io.onedev.utils.ExceptionUtils;
import io.onedev.server.web.component.avatarupload.AvatarUploadField;
import io.onedev.utils.FileUtils;
import io.onedev.utils.LockUtils;
import io.onedev.utils.StringUtils;
@ -53,7 +52,7 @@ public class DefaultAvatarManager implements AvatarManager {
} else {
File avatarFile = getUploaded(user);
if (avatarFile.exists()) {
return AVATARS_BASE_URL + "uploaded/users/" + user.getId() + "?version=" + avatarFile.lastModified();
return AVATARS_BASE_URL + "uploaded/users/" + user.getId() + ".jpg?version=" + avatarFile.lastModified();
}
if (configManager.getSystemSetting().isGravatarEnabled())
@ -71,7 +70,7 @@ public class DefaultAvatarManager implements AvatarManager {
if (StringUtils.isBlank(secondaryName))
secondaryName = primaryName;
File avatarFile = new File(Bootstrap.getSiteDir(), "avatars/generated/" + encoded);
File avatarFile = new File(Bootstrap.getSiteDir(), "avatars/generated/" + encoded + ".png");
if (!avatarFile.exists()) {
Lock avatarLock = LockUtils.getLock("generated-avatar:" + encoded);
avatarLock.lock();
@ -87,7 +86,7 @@ public class DefaultAvatarManager implements AvatarManager {
}
}
return AVATARS_BASE_URL + "generated/" + encoded;
return AVATARS_BASE_URL + "generated/" + encoded + ".png";
}
@Override
@ -117,26 +116,18 @@ public class DefaultAvatarManager implements AvatarManager {
@Override
public File getUploaded(UserFacade user) {
return new File(Bootstrap.getSiteDir(), "avatars/uploaded/users/" + user.getId());
return new File(Bootstrap.getSiteDir(), "avatars/uploaded/users/" + user.getId() + ".jpg");
}
@Sessional
@Override
public void useAvatar(UserFacade user, FileUpload upload) {
public void useAvatar(UserFacade user, String avatarData) {
Lock avatarLock = LockUtils.getLock("uploaded-user-avatar:" + user.getId());
avatarLock.lock();
try {
File avatarFile = getUploaded(user);
if (upload != null) {
FileUtils.createDir(avatarFile.getParentFile());
try {
upload.writeTo(avatarFile);
} catch (Exception e) {
throw ExceptionUtils.unchecked(e);
}
} else {
FileUtils.deleteFile(avatarFile);
}
FileUtils.createDir(avatarFile.getParentFile());
AvatarUploadField.writeToFile(avatarFile, avatarData);
} finally {
avatarLock.unlock();
}
@ -146,27 +137,19 @@ public class DefaultAvatarManager implements AvatarManager {
public String getAvatarUrl(ProjectFacade project) {
File avatarFile = getUploaded(project);
if (avatarFile.exists())
return AVATARS_BASE_URL + "uploaded/projects/" + project.getId() + "?version=" + avatarFile.lastModified();
return AVATARS_BASE_URL + "uploaded/projects/" + project.getId() + ".jpg?version=" + avatarFile.lastModified();
else
return AVATARS_BASE_URL + "project.png";
}
@Override
public void useAvatar(ProjectFacade project, FileUpload upload) {
public void useAvatar(ProjectFacade project, String avatarData) {
Lock avatarLock = LockUtils.getLock("uploaded-project-avatar:" + project.getId());
avatarLock.lock();
try {
File avatarFile = getUploaded(project);
if (upload != null) {
FileUtils.createDir(avatarFile.getParentFile());
try {
upload.writeTo(avatarFile);
} catch (Exception e) {
throw ExceptionUtils.unchecked(e);
}
} else {
FileUtils.deleteFile(avatarFile);
}
FileUtils.createDir(avatarFile.getParentFile());
AvatarUploadField.writeToFile(avatarFile, avatarData);
} finally {
avatarLock.unlock();
}
@ -174,7 +157,7 @@ public class DefaultAvatarManager implements AvatarManager {
@Override
public File getUploaded(ProjectFacade project) {
return new File(Bootstrap.getSiteDir(), "avatars/uploaded/projects/" + project.getId());
return new File(Bootstrap.getSiteDir(), "avatars/uploaded/projects/" + project.getId() + ".jpg");
}
@Override

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 13 KiB