Merge branch 'develop' into travis/invite-errors
This commit is contained in:
commit
0f348a7c78
25 changed files with 2153 additions and 239 deletions
|
@ -470,7 +470,7 @@ export default React.createClass({
|
|||
GroupStore.registerListener(groupId, this.onGroupStoreUpdated.bind(this, firstInit));
|
||||
let willDoOnboarding = false;
|
||||
// XXX: This should be more fluxy - let's get the error from GroupStore .getError or something
|
||||
GroupStore.on('error', (err, errorGroupId) => {
|
||||
GroupStore.on('error', (err, errorGroupId, stateKey) => {
|
||||
if (this._unmounted || groupId !== errorGroupId) return;
|
||||
if (err.errcode === 'M_GUEST_ACCESS_FORBIDDEN' && !willDoOnboarding) {
|
||||
dis.dispatch({
|
||||
|
@ -483,11 +483,13 @@ export default React.createClass({
|
|||
dis.dispatch({action: 'require_registration'});
|
||||
willDoOnboarding = true;
|
||||
}
|
||||
this.setState({
|
||||
summary: null,
|
||||
error: err,
|
||||
editing: false,
|
||||
});
|
||||
if (stateKey === GroupStore.STATE_KEY.Summary) {
|
||||
this.setState({
|
||||
summary: null,
|
||||
error: err,
|
||||
editing: false,
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -511,7 +513,6 @@ export default React.createClass({
|
|||
isUserMember: GroupStore.getGroupMembers(this.props.groupId).some(
|
||||
(m) => m.userId === this._matrixClient.credentials.userId,
|
||||
),
|
||||
error: null,
|
||||
});
|
||||
// XXX: This might not work but this.props.groupIsNew unused anyway
|
||||
if (this.props.groupIsNew && firstInit) {
|
||||
|
@ -1157,7 +1158,7 @@ export default React.createClass({
|
|||
|
||||
if (this.state.summaryLoading && this.state.error === null || this.state.saving) {
|
||||
return <Spinner />;
|
||||
} else if (this.state.summary) {
|
||||
} else if (this.state.summary && !this.state.error) {
|
||||
const summary = this.state.summary;
|
||||
|
||||
let avatarNode;
|
||||
|
|
|
@ -27,6 +27,7 @@ const React = require("react");
|
|||
const ReactDOM = require("react-dom");
|
||||
import PropTypes from 'prop-types';
|
||||
import Promise from 'bluebird';
|
||||
import filesize from 'filesize';
|
||||
const classNames = require("classnames");
|
||||
import { _t } from '../../languageHandler';
|
||||
|
||||
|
@ -101,6 +102,10 @@ module.exports = React.createClass({
|
|||
roomLoading: true,
|
||||
peekLoading: false,
|
||||
shouldPeek: true,
|
||||
|
||||
// Media limits for uploading.
|
||||
mediaConfig: undefined,
|
||||
|
||||
// used to trigger a rerender in TimelinePanel once the members are loaded,
|
||||
// so RR are rendered again (now with the members available), ...
|
||||
membersLoaded: !llMembers,
|
||||
|
@ -156,7 +161,7 @@ module.exports = React.createClass({
|
|||
MatrixClientPeg.get().on("RoomState.members", this.onRoomStateMember);
|
||||
MatrixClientPeg.get().on("Room.myMembership", this.onMyMembership);
|
||||
MatrixClientPeg.get().on("accountData", this.onAccountData);
|
||||
|
||||
this._fetchMediaConfig();
|
||||
// Start listening for RoomViewStore updates
|
||||
this._roomStoreToken = RoomViewStore.addListener(this._onRoomViewStoreUpdate);
|
||||
this._onRoomViewStoreUpdate(true);
|
||||
|
@ -164,6 +169,27 @@ module.exports = React.createClass({
|
|||
WidgetEchoStore.on('update', this._onWidgetEchoStoreUpdate);
|
||||
},
|
||||
|
||||
_fetchMediaConfig: function(invalidateCache: boolean = false) {
|
||||
/// NOTE: Using global here so we don't make repeated requests for the
|
||||
/// config every time we swap room.
|
||||
if(global.mediaConfig !== undefined && !invalidateCache) {
|
||||
this.setState({mediaConfig: global.mediaConfig});
|
||||
return;
|
||||
}
|
||||
console.log("[Media Config] Fetching");
|
||||
MatrixClientPeg.get().getMediaConfig().then((config) => {
|
||||
console.log("[Media Config] Fetched config:", config);
|
||||
return config;
|
||||
}).catch(() => {
|
||||
// Media repo can't or won't report limits, so provide an empty object (no limits).
|
||||
console.log("[Media Config] Could not fetch config, so not limiting uploads.");
|
||||
return {};
|
||||
}).then((config) => {
|
||||
global.mediaConfig = config;
|
||||
this.setState({mediaConfig: config});
|
||||
});
|
||||
},
|
||||
|
||||
_onRoomViewStoreUpdate: function(initial) {
|
||||
if (this.unmounted) {
|
||||
return;
|
||||
|
@ -499,6 +525,10 @@ module.exports = React.createClass({
|
|||
break;
|
||||
case 'notifier_enabled':
|
||||
case 'upload_failed':
|
||||
// 413: File was too big or upset the server in some way.
|
||||
if(payload.error.http_status === 413) {
|
||||
this._fetchMediaConfig(true);
|
||||
}
|
||||
case 'upload_started':
|
||||
case 'upload_finished':
|
||||
this.forceUpdate();
|
||||
|
@ -931,6 +961,15 @@ module.exports = React.createClass({
|
|||
this.setState({ draggingFile: false });
|
||||
},
|
||||
|
||||
isFileUploadAllowed(file) {
|
||||
if (this.state.mediaConfig !== undefined &&
|
||||
this.state.mediaConfig["m.upload.size"] !== undefined &&
|
||||
file.size > this.state.mediaConfig["m.upload.size"]) {
|
||||
return _t("File is too big. Maximum file size is %(fileSize)s", {fileSize: filesize(this.state.mediaConfig["m.upload.size"])});
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
uploadFile: async function(file) {
|
||||
dis.dispatch({action: 'focus_composer'});
|
||||
|
||||
|
@ -1687,6 +1726,7 @@ module.exports = React.createClass({
|
|||
callState={this.state.callState}
|
||||
disabled={this.props.disabled}
|
||||
showApps={this.state.showApps}
|
||||
uploadAllowed={this.isFileUploadAllowed}
|
||||
/>;
|
||||
}
|
||||
|
||||
|
|
|
@ -32,7 +32,9 @@ export default React.createClass({
|
|||
getInitialState: function() {
|
||||
return {
|
||||
members: null,
|
||||
membersError: null,
|
||||
invitedMembers: null,
|
||||
invitedMembersError: null,
|
||||
truncateAt: INITIAL_LOAD_NUM_MEMBERS,
|
||||
};
|
||||
},
|
||||
|
@ -50,6 +52,19 @@ export default React.createClass({
|
|||
GroupStore.registerListener(groupId, () => {
|
||||
this._fetchMembers();
|
||||
});
|
||||
GroupStore.on('error', (err, errorGroupId, stateKey) => {
|
||||
if (this._unmounted || groupId !== errorGroupId) return;
|
||||
if (stateKey === GroupStore.STATE_KEY.GroupMembers) {
|
||||
this.setState({
|
||||
membersError: err,
|
||||
});
|
||||
}
|
||||
if (stateKey === GroupStore.STATE_KEY.GroupInvitedMembers) {
|
||||
this.setState({
|
||||
invitedMembersError: err,
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
_fetchMembers: function() {
|
||||
|
@ -83,7 +98,11 @@ export default React.createClass({
|
|||
this.setState({ searchQuery: ev.target.value });
|
||||
},
|
||||
|
||||
makeGroupMemberTiles: function(query, memberList) {
|
||||
makeGroupMemberTiles: function(query, memberList, memberListError) {
|
||||
if (memberListError) {
|
||||
return <div className="warning">{ _t("Failed to load group members") }</div>;
|
||||
}
|
||||
|
||||
const GroupMemberTile = sdk.getComponent("groups.GroupMemberTile");
|
||||
const TruncatedList = sdk.getComponent("elements.TruncatedList");
|
||||
query = (query || "").toLowerCase();
|
||||
|
@ -153,15 +172,26 @@ export default React.createClass({
|
|||
);
|
||||
|
||||
const joined = this.state.members ? <div className="mx_MemberList_joined">
|
||||
{ this.makeGroupMemberTiles(this.state.searchQuery, this.state.members) }
|
||||
{
|
||||
this.makeGroupMemberTiles(
|
||||
this.state.searchQuery,
|
||||
this.state.members,
|
||||
this.state.membersError,
|
||||
)
|
||||
}
|
||||
</div> : <div />;
|
||||
|
||||
const invited = (this.state.invitedMembers && this.state.invitedMembers.length > 0) ?
|
||||
<div className="mx_MemberList_invited">
|
||||
<h2>{ _t("Invited") }</h2>
|
||||
{ this.makeGroupMemberTiles(this.state.searchQuery, this.state.invitedMembers) }
|
||||
<h2>{_t("Invited")}</h2>
|
||||
{
|
||||
this.makeGroupMemberTiles(
|
||||
this.state.searchQuery,
|
||||
this.state.invitedMembers,
|
||||
this.state.invitedMembersError,
|
||||
)
|
||||
}
|
||||
</div> : <div />;
|
||||
|
||||
return (
|
||||
<div className="mx_MemberList">
|
||||
{ inputBox }
|
||||
|
|
|
@ -139,7 +139,8 @@ export default class MessageComposer extends React.Component {
|
|||
}
|
||||
|
||||
onUploadFileSelected(files) {
|
||||
this.uploadFiles(files.target.files);
|
||||
const tfiles = files.target.files;
|
||||
this.uploadFiles(tfiles);
|
||||
}
|
||||
|
||||
uploadFiles(files) {
|
||||
|
@ -147,10 +148,21 @@ export default class MessageComposer extends React.Component {
|
|||
const TintableSvg = sdk.getComponent("elements.TintableSvg");
|
||||
|
||||
const fileList = [];
|
||||
const acceptedFiles = [];
|
||||
const failedFiles = [];
|
||||
|
||||
for (let i=0; i<files.length; i++) {
|
||||
fileList.push(<li key={i}>
|
||||
<TintableSvg key={i} src="img/files.svg" width="16" height="16" /> { files[i].name || _t('Attachment') }
|
||||
</li>);
|
||||
const fileAcceptedOrError = this.props.uploadAllowed(files[i]);
|
||||
if (fileAcceptedOrError === true) {
|
||||
acceptedFiles.push(<li key={i}>
|
||||
<TintableSvg key={i} src="img/files.svg" width="16" height="16" /> { files[i].name || _t('Attachment') }
|
||||
</li>);
|
||||
fileList.push(files[i]);
|
||||
} else {
|
||||
failedFiles.push(<li key={i}>
|
||||
<TintableSvg key={i} src="img/files.svg" width="16" height="16" /> { files[i].name || _t('Attachment') } <p>{ _t('Reason') + ": " + fileAcceptedOrError}</p>
|
||||
</li>);
|
||||
}
|
||||
}
|
||||
|
||||
const isQuoting = Boolean(RoomViewStore.getQuotingEvent());
|
||||
|
@ -161,23 +173,47 @@ export default class MessageComposer extends React.Component {
|
|||
}</p>;
|
||||
}
|
||||
|
||||
const acceptedFilesPart = acceptedFiles.length === 0 ? null : (
|
||||
<div>
|
||||
<p>{ _t('Are you sure you want to upload the following files?') }</p>
|
||||
<ul style={{listStyle: 'none', textAlign: 'left'}}>
|
||||
{ acceptedFiles }
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
|
||||
const failedFilesPart = failedFiles.length === 0 ? null : (
|
||||
<div>
|
||||
<p>{ _t('The following files cannot be uploaded:') }</p>
|
||||
<ul style={{listStyle: 'none', textAlign: 'left'}}>
|
||||
{ failedFiles }
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
let buttonText;
|
||||
if (acceptedFiles.length > 0 && failedFiles.length > 0) {
|
||||
buttonText = "Upload selected"
|
||||
} else if (failedFiles.length > 0) {
|
||||
buttonText = "Close"
|
||||
}
|
||||
|
||||
Modal.createTrackedDialog('Upload Files confirmation', '', QuestionDialog, {
|
||||
title: _t('Upload Files'),
|
||||
description: (
|
||||
<div>
|
||||
<p>{ _t('Are you sure you want to upload the following files?') }</p>
|
||||
<ul style={{listStyle: 'none', textAlign: 'left'}}>
|
||||
{ fileList }
|
||||
</ul>
|
||||
{ acceptedFilesPart }
|
||||
{ failedFilesPart }
|
||||
{ replyToWarning }
|
||||
</div>
|
||||
),
|
||||
hasCancelButton: acceptedFiles.length > 0,
|
||||
button: buttonText,
|
||||
onFinished: (shouldUpload) => {
|
||||
if (shouldUpload) {
|
||||
// MessageComposer shouldn't have to rely on its parent passing in a callback to upload a file
|
||||
if (files) {
|
||||
for (let i=0; i<files.length; i++) {
|
||||
this.props.uploadFile(files[i]);
|
||||
if (fileList) {
|
||||
for (let i=0; i<fileList.length; i++) {
|
||||
this.props.uploadFile(fileList[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -459,6 +495,9 @@ MessageComposer.propTypes = {
|
|||
// callback when a file to upload is chosen
|
||||
uploadFile: PropTypes.func.isRequired,
|
||||
|
||||
// function to test whether a file should be allowed to be uploaded.
|
||||
uploadAllowed: PropTypes.func.isRequired,
|
||||
|
||||
// string representing the current room app drawer state
|
||||
showApps: PropTypes.bool,
|
||||
showApps: PropTypes.bool
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue