PLT-6277 Moved profile image cropping to server (#6269)

* PLT-6277 Moved profile image cropping to server

* Cosmetic refactoring of SettingPicture component
Этот коммит содержится в:
Harrison Healey
2017-05-03 09:08:00 -04:00
коммит произвёл Christopher Speller
родитель b509f65b8e
Коммит 1c8fdb4cdd
4 изменённых файлов: 71 добавлений и 115 удалений

Просмотреть файл

@@ -805,7 +805,7 @@ func SetProfileImage(userId string, imageData *multipart.FileHeader) *model.AppE
}
// Scale profile image
img = imaging.Resize(img, utils.Cfg.FileSettings.ProfileWidth, utils.Cfg.FileSettings.ProfileHeight, imaging.Lanczos)
img = imaging.Fill(img, utils.Cfg.FileSettings.ProfileWidth, utils.Cfg.FileSettings.ProfileHeight, imaging.Center, imaging.Lanczos)
buf := new(bytes.Buffer)
err = png.Encode(buf, img)

Просмотреть файл

@@ -1,74 +1,63 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React, {Component, PropTypes} from 'react';
import {FormattedMessage} from 'react-intl';
import FormError from 'components/form_error.jsx';
import loadingGif from 'images/load.gif';
import React from 'react';
export default class SettingPicture extends Component {
static propTypes = {
clientError: PropTypes.string,
serverError: PropTypes.string,
src: PropTypes.string,
file: PropTypes.object,
loadingPicture: PropTypes.bool,
submitActive: PropTypes.bool,
submit: PropTypes.func,
title: PropTypes.string,
onFileChange: PropTypes.func,
updateSection: PropTypes.func
};
export default class SettingPicture extends React.Component {
constructor(props) {
super(props);
this.setPicture = this.setPicture.bind(this);
this.confirmImage = this.confirmImage.bind(this);
this.state = {
image: null
};
}
setPicture(file) {
componentWillReceiveProps(nextProps) {
if (nextProps.file !== this.props.file) {
this.setState({image: null});
this.setPicture(nextProps.file);
}
}
setPicture = (file) => {
if (file) {
var reader = new FileReader();
reader.onload = (e) => {
const canvas = this.refs.profileImageCanvas;
const context = canvas.getContext('2d');
const imageObj = new Image();
imageObj.onload = () => {
if (imageObj.width > imageObj.height) {
const side = imageObj.height;
const rem = imageObj.width - side;
const startX = parseInt(rem / 2, 10);
context.drawImage(imageObj, startX, 0, side, side,
0, 0, canvas.width, canvas.height);
} else {
const side = imageObj.width;
const rem = imageObj.height - side;
const startY = parseInt(rem / 2, 10);
context.drawImage(imageObj, 0, startY, side, side,
0, 0, canvas.width, canvas.height);
}
};
imageObj.src = e.target.result;
this.setState({
image: e.target.result
});
};
reader.readAsDataURL(file);
}
}
componentWillReceiveProps(nextProps) {
if (nextProps.picture) {
this.setPicture(nextProps.picture);
}
}
render() {
var clientError = null;
if (this.props.client_error) {
clientError = <div className='form-group has-error'><label className='control-label'>{this.props.client_error}</label></div>;
}
var serverError = null;
if (this.props.server_error) {
serverError = <div className='form-group has-error'><label className='control-label'>{this.props.server_error}</label></div>;
}
var img = null;
if (this.props.picture) {
let img;
if (this.props.file) {
img = (
<canvas
ref='profileImageCanvas'
className='profile-img'
width='256px'
height='256px'
<div
className='profile-img-preview'
style={{backgroundImage: 'url(' + this.state.image + ')'}}
/>
);
} else {
@@ -81,7 +70,7 @@ export default class SettingPicture extends React.Component {
);
}
var confirmButton;
let confirmButton;
if (this.props.loadingPicture) {
confirmButton = (
<img
@@ -90,7 +79,7 @@ export default class SettingPicture extends React.Component {
/>
);
} else {
var confirmButtonClass = 'btn btn-sm';
let confirmButtonClass = 'btn btn-sm';
if (this.props.submitActive) {
confirmButtonClass += ' btn-primary';
} else {
@@ -100,7 +89,7 @@ export default class SettingPicture extends React.Component {
confirmButton = (
<a
className={confirmButtonClass}
onClick={this.confirmImage}
onClick={this.props.submit}
>
<FormattedMessage
id='setting_picture.save'
@@ -109,18 +98,7 @@ export default class SettingPicture extends React.Component {
</a>
);
}
var helpText = (
<FormattedMessage
id='setting_picture.help'
defaultMessage='Upload a profile picture in BMP, JPG, JPEG or PNG format, at least {width}px in width and {height}px height.'
values={{
width: global.window.mm_config.ProfileWidth,
height: global.window.mm_config.ProfileHeight
}}
/>
);
var self = this;
return (
<ul className='section-max'>
<li className='col-xs-12 section-title'>{this.props.title}</li>
@@ -130,11 +108,17 @@ export default class SettingPicture extends React.Component {
{img}
</li>
<li className='setting-list-item'>
{helpText}
<FormattedMessage
id='setting_picture.help'
defaultMessage='Upload a profile picture in BMP, JPG, JPEG or PNG format, at least {width}px in width and {height}px height.'
values={{
width: global.mm_config.ProfileWidth,
height: global.mm_config.ProfileHeight
}}
/>
</li>
<li className='setting-list-item'>
{serverError}
{clientError}
<FormError errors={[this.props.clientError, this.props.serverError]}/>
<span className='btn btn-sm btn-primary btn-file sel-btn'>
<FormattedMessage
id='setting_picture.select'
@@ -144,14 +128,14 @@ export default class SettingPicture extends React.Component {
ref='input'
accept='.jpg,.png,.bmp'
type='file'
onChange={this.props.pictureChange}
onChange={this.props.onFileChange}
/>
</span>
{confirmButton}
<a
className='btn btn-sm theme'
href='#'
onClick={self.props.updateSection}
onClick={this.props.updateSection}
>
<FormattedMessage
id='setting_picture.cancel'
@@ -164,27 +148,4 @@ export default class SettingPicture extends React.Component {
</ul>
);
}
confirmImage(e) {
e.persist();
this.refs.profileImageCanvas.toBlob((blob) => {
blob.lastModifiedDate = new Date();
blob.name = 'image.jpg';
this.props.imageCropChange(blob);
this.props.submit(e);
}, 'image/jpeg', 0.95);
}
}
SettingPicture.propTypes = {
client_error: React.PropTypes.string,
server_error: React.PropTypes.string,
src: React.PropTypes.string,
picture: React.PropTypes.object,
loadingPicture: React.PropTypes.bool,
submitActive: React.PropTypes.bool,
submit: React.PropTypes.func,
title: React.PropTypes.string,
pictureChange: React.PropTypes.func,
imageCropChange: React.PropTypes.func
};

Просмотреть файл

@@ -113,7 +113,6 @@ class UserSettingsGeneralTab extends React.Component {
this.updatePicture = this.updatePicture.bind(this);
this.updateSection = this.updateSection.bind(this);
this.updatePosition = this.updatePosition.bind(this);
this.updatedCroppedPicture = this.updatedCroppedPicture.bind(this);
this.state = this.setupInitialState(props);
}
@@ -241,7 +240,7 @@ class UserSettingsGeneralTab extends React.Component {
submitPicture(e) {
e.preventDefault();
if (!this.state.picture) {
if (!this.state.pictureFile) {
return;
}
@@ -252,12 +251,12 @@ class UserSettingsGeneralTab extends React.Component {
trackEvent('settings', 'user_settings_update', {field: 'picture'});
const {formatMessage} = this.props.intl;
const picture = this.state.picture;
const file = this.state.pictureFile;
if (picture.type !== 'image/jpeg' && picture.type !== 'image/png') {
if (file.type !== 'image/jpeg' && file.type !== 'image/png') {
this.setState({clientError: formatMessage(holders.validImage)});
return;
} else if (picture.size > this.state.maxFileSize) {
} else if (file.size > this.state.maxFileSize) {
this.setState({clientError: formatMessage(holders.imageTooLarge)});
return;
}
@@ -265,7 +264,7 @@ class UserSettingsGeneralTab extends React.Component {
this.setState({loadingPicture: true});
uploadProfileImage(
picture,
file,
() => {
this.updateSection('');
this.submitActive = false;
@@ -324,25 +323,14 @@ class UserSettingsGeneralTab extends React.Component {
this.setState({confirmEmail: e.target.value});
}
updatedCroppedPicture(file) {
if (file) {
this.setState({picture: file});
this.submitActive = true;
this.setState({clientError: null});
} else {
this.setState({picture: null});
}
}
updatePicture(e) {
if (e.target.files && e.target.files[0]) {
this.setState({picture: e.target.files[0]});
this.setState({pictureFile: e.target.files[0]});
this.submitActive = true;
this.setState({clientError: null});
} else {
this.setState({picture: null});
this.setState({pictureFile: null});
}
}
@@ -368,7 +356,7 @@ class UserSettingsGeneralTab extends React.Component {
originalEmail: user.email,
email: '',
confirmEmail: '',
picture: null,
pictureFile: null,
loadingPicture: false,
emailChangeInProgress: false,
maxFileSize: global.window.mm_config.MaxFileSize
@@ -1124,17 +1112,16 @@ class UserSettingsGeneralTab extends React.Component {
title={formatMessage(holders.profilePicture)}
submit={this.submitPicture}
src={Client.getUsersRoute() + '/' + user.id + '/image?time=' + user.last_picture_update}
server_error={serverError}
client_error={clientError}
serverError={serverError}
clientError={clientError}
updateSection={(e) => {
this.updateSection('');
e.preventDefault();
}}
picture={this.state.picture}
pictureChange={this.updatePicture}
file={this.state.pictureFile}
onFileChange={this.updatePicture}
submitActive={this.submitActive}
loadingPicture={this.state.loadingPicture}
imageCropChange={this.updatedCroppedPicture}
/>
);
} else {

Просмотреть файл

@@ -95,6 +95,14 @@
width: 128px;
}
.profile-img-preview {
background-position: 50% 50%;
background-size: cover;
border-radius: 100%;
height: 128px;
width: 128px;
}
.settings-table {
display: table;
margin: 0 auto;