fix avatar upload on signup

This commit is contained in:
or-else
2022-09-01 11:12:09 -07:00
parent ee106a17f6
commit d45a141704
12 changed files with 612 additions and 536 deletions
+71 -6
View File
@@ -2,14 +2,17 @@
import React from 'react';
import { FormattedMessage } from 'react-intl';
import AvatarCrop from '../widgets/avatar-crop.jsx';
import AvatarUpload from '../widgets/avatar-upload.jsx';
import CheckBox from '../widgets/checkbox.jsx';
import VisiblePassword from '../widgets/visible-password.jsx';
import LocalStorageUtil from '../lib/local-storage.js';
import { imageScaled, blobToBase64, makeImageUrl } from '../lib/blob-helpers.js';
import { theCard } from '../lib/utils.js';
import { MAX_TITLE_LENGTH } from '../config.js';
import { AVATAR_SIZE, MAX_AVATAR_BYTES, MAX_EXTERN_ATTACHMENT_SIZE,
MAX_TITLE_LENGTH } from '../config.js';
export default class CreateAccountView extends React.PureComponent {
constructor(props) {
@@ -20,8 +23,12 @@ export default class CreateAccountView extends React.PureComponent {
password: '',
email: '',
fn: '', // full/formatted name
imageDataUrl: null,
imageUrl: null,
uploadUrl: null,
newAvatar: null,
newAvatarMime: null,
errorCleared: false,
buttonDisabled: false,
saveToken: LocalStorageUtil.getObject('keep-logged-in')
};
@@ -31,6 +38,9 @@ export default class CreateAccountView extends React.PureComponent {
this.handleFnChange = this.handleFnChange.bind(this);
this.handleImageChanged = this.handleImageChanged.bind(this);
this.handleToggleSaveToken = this.handleToggleSaveToken.bind(this);
this.handleAvatarCropped = this.handleAvatarCropped.bind(this);
this.handleAvatarCropCancel = this.handleAvatarCropCancel.bind(this);
this.uploadAvatar = this.uploadAvatar.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
@@ -51,7 +61,7 @@ export default class CreateAccountView extends React.PureComponent {
}
handleImageChanged(mime, img) {
this.setState({imageDataUrl: img});
this.setState({newAvatar: img, newAvatarMime: mime});
}
handleToggleSaveToken() {
@@ -65,11 +75,66 @@ export default class CreateAccountView extends React.PureComponent {
this.props.onCreateAccount(
this.state.login.trim(),
this.state.password.trim(),
theCard(this.state.fn.trim().substring(0, MAX_TITLE_LENGTH), this.state.imageDataUrl),
theCard(this.state.fn.trim().substring(0, MAX_TITLE_LENGTH), this.state.uploadUrl),
{'meth': 'email', 'val': this.state.email});
}
// AvatarCropView calls this method when the user has cropped the image.
handleAvatarCropped(mime, blob, width, height) {
const url = blob ? URL.createObjectURL(blob) : null;
this.setState({avatar: url, newAvatar: null, newAvatarMime: null});
if (blob) {
this.uploadAvatar(mime, blob, width, height);
}
}
handleAvatarCropCancel() {
this.setState({newAvatar: null, newAvatarMime: null});
}
// 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 = (image) => {
let {mime, blob} = image;
this.setState({imageUrl: URL.createObjectURL(blob), buttonDisabled: true});
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();
uploader.upload(blob, 'newacc')
.then(url => this.setState({uploadUrl: url}))
.catch(err => this.props.onError(err.message, 'err'))
.finally(_ => this.setState({buttonDisabled: false}));
} else {
// Convert blob to base64-encoded bits.
blobToBase64(blob)
.then(b64 => this.setState({uploadUrl: makeImageUrl({data: b64.bits, type: mime})}))
.finally(_ => this.setState({buttonDisabled: false}));
}
};
if (width > AVATAR_SIZE || height > AVATAR_SIZE || width != height) {
// Avatar is not square or too large even after cropping. Shrink it and make square.
imageScaled(blob, AVATAR_SIZE, AVATAR_SIZE, MAX_EXTERN_ATTACHMENT_SIZE, true)
.then(scaled => readyToUpload(scaled))
.catch(err => this.props.onError(err.message, 'err'));
} else {
readyToUpload({mime: mime, blob: blob, width: width, height: height});
}
}
render() {
if (this.state.newAvatar) {
return (
<AvatarCrop
avatar={this.state.newAvatar}
mime={this.state.newAvatarMime}
onSubmit={this.handleAvatarCropped}
onCancel={this.handleAvatarCropCancel}
onError={this.props.onError} />
);
}
let submitClasses = 'primary';
if (this.props.disabled) {
submitClasses += ' disabled';
@@ -93,7 +158,7 @@ export default class CreateAccountView extends React.PureComponent {
</div>
<AvatarUpload
tinode={this.props.tinode}
avatar={this.state.imageDataUrl}
avatar={this.state.imageUrl}
onImageUpdated={this.handleImageChanged}
onError={this.props.onError} />
</div>
@@ -120,7 +185,7 @@ export default class CreateAccountView extends React.PureComponent {
}</FormattedMessage>
</div>
<div className="dialog-buttons">
<button className={submitClasses} type="submit">
<button className={submitClasses} type="submit" disabled={this.state.buttonDisabled}>
<FormattedMessage id="button_sign_up" defaultMessage="Sign up"
description="Create account button [Sign Up]" />
</button>
+8 -4
View File
@@ -1125,16 +1125,20 @@ class TinodeWeb extends React.Component {
this.handleError();
this.tinode.connect(this.state.serverAddress)
.then(() => {
.then(_ => {
let attachments;
if (public_ && public_.photo && public_.photo.ref) {
attachments = [public_.photo.ref];
}
return this.tinode.createAccountBasic(login_, password_,
{public: public_, tags: tags_, cred: Tinode.credential(cred_)});
}).then((ctrl) => {
{public: public_, tags: tags_, cred: Tinode.credential(cred_), attachments: attachments});
}).then(ctrl => {
if (ctrl.code >= 300 && ctrl.text == 'validate credentials') {
TinodeWeb.navigateToCredentialsView(ctrl.params);
} else {
this.handleLoginSuccessful(this);
}
}).catch((err) => {
}).catch(err => {
this.handleError(err.message, 'err');
});
}
+6 -17
View File
@@ -39,10 +39,10 @@ export default class TopicDescEdit extends React.Component {
this.handleFullNameUpdate = this.handleFullNameUpdate.bind(this);
this.handleImageUpdated = this.handleImageUpdated.bind(this);
this.handleAvatarCropped = this.handleAvatarCropped.bind(this);
this.handleAvatarCropCancel = this.handleAvatarCropCancel.bind(this);
this.uploadAvatar = this.uploadAvatar.bind(this);
this.handlePrivateUpdate = this.handlePrivateUpdate.bind(this);
this.handleDescriptionUpdate = this.handleDescriptionUpdate.bind(this);
this.uploadAvatar = this.uploadAvatar.bind(this);
this.handleAvatarCropCancel = this.handleAvatarCropCancel.bind(this);
this.handleTagsUpdated = this.handleTagsUpdated.bind(this);
}
@@ -113,27 +113,16 @@ export default class TopicDescEdit extends React.Component {
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();
this.setState({uploading: true});
uploader.upload(blob)
.then((url) => {
this.props.onUpdateTopicDesc(this.props.topic, theCard(null, url));
})
.catch((err) => {
this.props.onError(err, 'err');
})
.finally(() => {
this.setState({uploading: false});
});
.then(url => this.props.onUpdateTopicDesc(this.props.topic, theCard(null, url)))
.catch(err => this.props.onError(err.message, 'err'));
} else {
this.setState({uploading: true});
// Convert blob to base64-encoded bits.
blobToBase64(blob)
.then(b64 => {
const du = makeImageUrl({data: b64.bits, type: mime});
this.setState({source: du});
this.props.onUpdateTopicDesc(this.props.topic, theCard(null, du));
this.setState({uploading: false});
});
}
};
@@ -142,13 +131,13 @@ export default class TopicDescEdit extends React.Component {
// Avatar is not square or too large even after cropping. Shrink it and make square.
imageScaled(blob, AVATAR_SIZE, AVATAR_SIZE, MAX_EXTERN_ATTACHMENT_SIZE, true)
.then(scaled => readyToUpload(scaled))
.catch(err => this.props.onError(err, 'err'));
.catch(err => this.props.onError(err.message, 'err'));
} else {
readyToUpload({mime: mime, blob: blob, width: width, height: height});
}
}
handleAvatarCropCancel(img) {
handleAvatarCropCancel() {
this.setState({newAvatar: null, newAvatarMime: null});
}