making uploads work
This commit is contained in:
@@ -10,8 +10,9 @@ import AvatarUpload from '../widgets/avatar-upload.jsx';
|
|||||||
import InPlaceEdit from '../widgets/in-place-edit.jsx';
|
import InPlaceEdit from '../widgets/in-place-edit.jsx';
|
||||||
import TagManager from '../widgets/tag-manager.jsx';
|
import TagManager from '../widgets/tag-manager.jsx';
|
||||||
|
|
||||||
import { MAX_TITLE_LENGTH } from '../config.js';
|
import { AVATAR_SIZE, MAX_AVATAR_BYTES, MAX_EXTERN_ATTACHMENT_SIZE,
|
||||||
import { makeImageDataUrl } from '../lib/blob-helpers.js';
|
MAX_INBAND_ATTACHMENT_SIZE, MAX_TITLE_LENGTH } from '../config.js';
|
||||||
|
import { imageScaled, blobToBase64, makeImageDataUrl } from '../lib/blob-helpers.js';
|
||||||
import { arrayEqual, asEmail, asPhone, theCard } from '../lib/utils.js';
|
import { arrayEqual, asEmail, asPhone, theCard } from '../lib/utils.js';
|
||||||
|
|
||||||
export default class AccGeneralView extends React.Component {
|
export default class AccGeneralView extends React.Component {
|
||||||
@@ -35,9 +36,10 @@ export default class AccGeneralView extends React.Component {
|
|||||||
this.tnNewTags = this.tnNewTags.bind(this);
|
this.tnNewTags = this.tnNewTags.bind(this);
|
||||||
this.tnCredsUpdated = this.tnCredsUpdated.bind(this);
|
this.tnCredsUpdated = this.tnCredsUpdated.bind(this);
|
||||||
this.handleFullNameUpdate = this.handleFullNameUpdate.bind(this);
|
this.handleFullNameUpdate = this.handleFullNameUpdate.bind(this);
|
||||||
this.handleImageUploaded = this.handleImageUploaded.bind(this);
|
this.handleImageUpdated = this.handleImageUpdated.bind(this);
|
||||||
this.handleAvatarCropped = this.handleAvatarCropped.bind(this);
|
this.handleAvatarCropped = this.handleAvatarCropped.bind(this);
|
||||||
this.handleCancel = this.handleCancel.bind(this);
|
this.uploadAvatar = this.uploadAvatar.bind(this);
|
||||||
|
this.handleAvatarCropCancel = this.handleAvatarCropCancel.bind(this);
|
||||||
this.handleCredChange = this.handleCredChange.bind(this);
|
this.handleCredChange = this.handleCredChange.bind(this);
|
||||||
this.handleCredKeyDown = this.handleCredKeyDown.bind(this);
|
this.handleCredKeyDown = this.handleCredKeyDown.bind(this);
|
||||||
this.handleCredEntered = this.handleCredEntered.bind(this);
|
this.handleCredEntered = this.handleCredEntered.bind(this);
|
||||||
@@ -72,17 +74,71 @@ export default class AccGeneralView extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleImageUploaded(mime, img) {
|
// AvatarUpload widget has changed the avatar: received a new file or deleted the avatar.
|
||||||
|
handleImageUpdated(mime, img) {
|
||||||
|
// This will show the AvatarCropView is img is not null.
|
||||||
this.setState({newAvatar: img, newAvatarMime: mime});
|
this.setState({newAvatar: img, newAvatarMime: mime});
|
||||||
|
if (!img) {
|
||||||
|
// Deleting the avatar.
|
||||||
|
this.props.onUpdateAccount(undefined, theCard(null, Tinode.DEL_CHAR));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AvatarCropView calls this method when the user has cropped the image.
|
||||||
handleAvatarCropped(mime, blob, width, height) {
|
handleAvatarCropped(mime, blob, width, height) {
|
||||||
const url = blob ? URL.createObjectURL(blob) : null;
|
const url = blob ? URL.createObjectURL(blob) : null;
|
||||||
this.setState({avatar: url, newAvatar: null, newAvatarMime: null});
|
this.setState({avatar: url, newAvatar: null, newAvatarMime: null});
|
||||||
// this.props.onUpdateAccount(undefined, theCard(null, img || Tinode.DEL_CHAR));
|
if (blob) {
|
||||||
|
this.uploadAvatar(mime, blob, width, height);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleCancel(img) {
|
// Utility method for converting cropped avatar blob to bytes for sending inband or
|
||||||
|
// for uploading it to the server out of band.
|
||||||
|
uploadAvatar(mime, blob, width, height) {
|
||||||
|
const readyToUpload = (mime, blob, width, height) => {
|
||||||
|
if (blob.size > MAX_AVATAR_BYTES) {
|
||||||
|
// Too large to send inband - uploading out of band and sending as a link.
|
||||||
|
const uploader = this.props.tinode.getLargeFileHelper();
|
||||||
|
if (!uploader) {
|
||||||
|
this.props.onError(this.props.intl.formatMessage(messages.cannot_initiate_upload));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({uploading: true});
|
||||||
|
uploader.upload(blob)
|
||||||
|
.then((url) => {
|
||||||
|
this.props.onUpdateAccount(undefined, theCard(null, url));
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
this.props.onError(err, 'err');
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.setState({uploading: false});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Convert blob to base64-encoded bits.
|
||||||
|
blobToBase64(blob, (unused, base64bits) => {
|
||||||
|
const du = makeImageDataUrl({data: base64bits, type: mime});
|
||||||
|
this.setState({source: du});
|
||||||
|
this.props.onUpdateAccount(undefined, theCard(null, du));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (width > AVATAR_SIZE || height > AVATAR_SIZE) {
|
||||||
|
// Avatar is too large even after scaling. Shring it and make square.
|
||||||
|
imageScaled(blob, AVATAR_SIZE, AVATAR_SIZE, MAX_EXTERN_ATTACHMENT_SIZE, true,
|
||||||
|
readyToUpload,
|
||||||
|
(err) => {
|
||||||
|
this.props.onError(err, 'err');
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
readyToUpload(mime, blob, width, height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleAvatarCropCancel(img) {
|
||||||
this.setState({newAvatar: null, newAvatarMime: null});
|
this.setState({newAvatar: null, newAvatarMime: null});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,7 +196,7 @@ export default class AccGeneralView extends React.Component {
|
|||||||
avatar={this.state.newAvatar}
|
avatar={this.state.newAvatar}
|
||||||
mime={this.state.newAvatarMime}
|
mime={this.state.newAvatarMime}
|
||||||
onSubmit={this.handleAvatarCropped}
|
onSubmit={this.handleAvatarCropped}
|
||||||
onCancel={this.handleCancel}
|
onCancel={this.handleAvatarCropCancel}
|
||||||
onError={this.props.onError} />
|
onError={this.props.onError} />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -176,8 +232,7 @@ export default class AccGeneralView extends React.Component {
|
|||||||
avatar={this.state.avatar}
|
avatar={this.state.avatar}
|
||||||
uid={this.props.myUserId}
|
uid={this.props.myUserId}
|
||||||
title={this.state.fullName}
|
title={this.state.fullName}
|
||||||
onImageReceived={this.handleImageUploaded}
|
onImageUpdated={this.handleImageUpdated}
|
||||||
onImageChanged={this.handleImageChanged}
|
|
||||||
onError={this.props.onError} />
|
onError={this.props.onError} />
|
||||||
</div>
|
</div>
|
||||||
<div className="hr" />
|
<div className="hr" />
|
||||||
|
|||||||
@@ -605,7 +605,7 @@ class InfoView extends React.Component {
|
|||||||
readOnly={!this.state.owner}
|
readOnly={!this.state.owner}
|
||||||
uid={this.props.topic}
|
uid={this.props.topic}
|
||||||
title={this.state.fullName}
|
title={this.state.fullName}
|
||||||
onImageChanged={this.handleImageChanged}
|
onImageUpdated={this.handleImageChanged}
|
||||||
onError={this.props.onError} />
|
onError={this.props.onError} />
|
||||||
</div>
|
</div>
|
||||||
<div className="hr" />
|
<div className="hr" />
|
||||||
|
|||||||
@@ -3,20 +3,17 @@ import React from 'react';
|
|||||||
import LetterTile from './letter-tile.jsx';
|
import LetterTile from './letter-tile.jsx';
|
||||||
import LoadSpinner from './load-spinner.jsx';
|
import LoadSpinner from './load-spinner.jsx';
|
||||||
|
|
||||||
import { AVATAR_SIZE, IMAGE_PREVIEW_DIM, MAX_AVATAR_BYTES, MAX_EXTERN_ATTACHMENT_SIZE,
|
import { AVATAR_SIZE } from '../config.js';
|
||||||
MAX_INBAND_ATTACHMENT_SIZE } from '../config.js';
|
|
||||||
import { imageScaled, blobToBase64, makeImageDataUrl } from '../lib/blob-helpers.js';
|
|
||||||
|
|
||||||
export default class AvatarUpload extends React.Component {
|
export default class AvatarUpload extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
source: props.avatar,
|
source: props.avatar
|
||||||
uploading: false
|
|
||||||
};
|
};
|
||||||
|
|
||||||
this.handleFileUpload = this.handleFileUpload.bind(this);
|
this.handleFileReceived = this.handleFileReceived.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps) {
|
||||||
@@ -25,47 +22,9 @@ export default class AvatarUpload extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleFileUpload(e) {
|
handleFileReceived(e) {
|
||||||
const image = e.target.files[0];
|
const image = e.target.files[0];
|
||||||
if (this.props.onImageReceived) {
|
this.props.onImageUpdated(image.type, URL.createObjectURL(image), image.name);
|
||||||
this.props.onImageReceived(image.type, URL.createObjectURL(image));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (image.size > MAX_AVATAR_BYTES) {
|
|
||||||
// Too large to send inband - uploading out of band and sending as a link.
|
|
||||||
const uploader = this.props.tinode.getLargeFileHelper();
|
|
||||||
if (!uploader) {
|
|
||||||
this.props.onError(this.props.intl.formatMessage(messages.cannot_initiate_upload));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
uploader.upload(image)
|
|
||||||
.then((url) => {
|
|
||||||
this.props.onImageChanged(url);
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
this.props.onError(err, 'err');
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
this.setState({uploading: false});
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
imageScaled(image, AVATAR_SIZE, AVATAR_SIZE, MAX_EXTERN_ATTACHMENT_SIZE, true,
|
|
||||||
// Image successfully scaled and converted.
|
|
||||||
(mime, blob) => {
|
|
||||||
// Convert blob to base64-encoded bits.
|
|
||||||
blobToBase64(blob, (unused, base64bits) => {
|
|
||||||
const du = makeImageDataUrl({data: base64bits, type: mime});
|
|
||||||
this.setState({source: du});
|
|
||||||
this.props.onImageChanged(du);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
// Failure
|
|
||||||
(err) => {
|
|
||||||
this.props.onError(err, 'err');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// Clear the value so the same file can be uploaded again.
|
// Clear the value so the same file can be uploaded again.
|
||||||
e.target.value = '';
|
e.target.value = '';
|
||||||
}
|
}
|
||||||
@@ -79,7 +38,7 @@ export default class AvatarUpload extends React.Component {
|
|||||||
<div className={className}>
|
<div className={className}>
|
||||||
{this.props.readOnly || !this.state.source ?
|
{this.props.readOnly || !this.state.source ?
|
||||||
null :
|
null :
|
||||||
<a href="#" className="clear-avatar" onClick={(e) => {e.preventDefault(); this.props.onImageChanged(null);}}>
|
<a href="#" className="clear-avatar" onClick={(e) => {e.preventDefault(); this.props.onImageUpdated();}}>
|
||||||
<i className="material-icons">clear</i>
|
<i className="material-icons">clear</i>
|
||||||
</a>}
|
</a>}
|
||||||
{this.state.source ?
|
{this.state.source ?
|
||||||
@@ -95,12 +54,12 @@ export default class AvatarUpload extends React.Component {
|
|||||||
<div className="blank">{AVATAR_SIZE}×{AVATAR_SIZE}</div>}
|
<div className="blank">{AVATAR_SIZE}×{AVATAR_SIZE}</div>}
|
||||||
{this.props.readOnly ? null :
|
{this.props.readOnly ? null :
|
||||||
<input type="file" id={randId} className="inputfile hidden"
|
<input type="file" id={randId} className="inputfile hidden"
|
||||||
accept="image/*" onChange={this.handleFileUpload} />}
|
accept="image/*" onChange={this.handleFileReceived} />}
|
||||||
{this.props.readOnly ? null :
|
{this.props.readOnly ? null :
|
||||||
<label htmlFor={randId} className="round">
|
<label htmlFor={randId} className="round">
|
||||||
<i className="material-icons">file_upload</i>
|
<i className="material-icons">file_upload</i>
|
||||||
</label>}
|
</label>}
|
||||||
<LoadSpinner show={this.state.uploading} large={true} clear={true} centered={true} />
|
<LoadSpinner show={this.props.uploading} large={true} clear={true} centered={true} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user