diff --git a/.eslintignore.errorfiles b/.eslintignore.errorfiles index f1b63d7367..4584d7ed65 100644 --- a/.eslintignore.errorfiles +++ b/.eslintignore.errorfiles @@ -22,7 +22,6 @@ src/components/structures/login/ForgotPassword.js src/components/structures/login/Login.js src/components/structures/login/PostRegistration.js src/components/structures/login/Registration.js -src/components/structures/MatrixChat.js src/components/structures/MessagePanel.js src/components/structures/NotificationPanel.js src/components/structures/RoomStatusBar.js @@ -30,7 +29,6 @@ src/components/structures/RoomView.js src/components/structures/ScrollPanel.js src/components/structures/TimelinePanel.js src/components/structures/UploadBar.js -src/components/structures/UserSettings.js src/components/views/avatars/BaseAvatar.js src/components/views/avatars/MemberAvatar.js src/components/views/avatars/RoomAvatar.js diff --git a/scripts/copy-i18n.py b/scripts/copy-i18n.py new file mode 100755 index 0000000000..07b1271239 --- /dev/null +++ b/scripts/copy-i18n.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python + +import json +import sys +import os + +if len(sys.argv) < 3: + print "Usage: %s " % (sys.argv[0],) + print "eg. %s pt_BR.json pt.json" % (sys.argv[0],) + print + print "Adds any translations to that exist in but not " + sys.exit(1) + +srcpath = sys.argv[1] +dstpath = sys.argv[2] +tmppath = dstpath + ".tmp" + +with open(srcpath) as f: + src = json.load(f) + +with open(dstpath) as f: + dst = json.load(f) + +toAdd = {} +for k,v in src.iteritems(): + if k not in dst: + print "Adding %s" % (k,) + toAdd[k] = v + +# don't just json.dumps as we'll probably re-order all the keys (and they're +# not in any given order so we can't just sort_keys). Append them to the end. +with open(dstpath) as ifp: + with open(tmppath, 'w') as ofp: + for line in ifp: + strippedline = line.strip() + if strippedline in ('{', '}'): + ofp.write(line) + elif strippedline.endswith(','): + ofp.write(line) + else: + ofp.write(' '+strippedline+',') + toAddStr = json.dumps(toAdd, indent=4, separators=(',', ': '), ensure_ascii=False, encoding="utf8").strip("{}\n") + ofp.write("\n") + ofp.write(toAddStr.encode('utf8')) + ofp.write("\n") + +os.rename(tmppath, dstpath) diff --git a/src/CallHandler.js b/src/CallHandler.js index b2ccf65df7..e3fbe9e5e3 100644 --- a/src/CallHandler.js +++ b/src/CallHandler.js @@ -51,13 +51,14 @@ limitations under the License. * } */ -var MatrixClientPeg = require('./MatrixClientPeg'); -var PlatformPeg = require("./PlatformPeg"); -var Modal = require('./Modal'); -var sdk = require('./index'); +import MatrixClientPeg from './MatrixClientPeg'; +import UserSettingsStore from './UserSettingsStore'; +import PlatformPeg from './PlatformPeg'; +import Modal from './Modal'; +import sdk from './index'; import { _t } from './languageHandler'; -var Matrix = require("matrix-js-sdk"); -var dis = require("./dispatcher"); +import Matrix from 'matrix-js-sdk'; +import dis from './dispatcher'; global.mxCalls = { //room_id: MatrixCall @@ -257,9 +258,9 @@ function _onAction(payload) { } else if (members.length === 2) { console.log("Place %s call in %s", payload.type, payload.room_id); - var call = Matrix.createNewMatrixCall( - MatrixClientPeg.get(), payload.room_id - ); + const call = Matrix.createNewMatrixCall(MatrixClientPeg.get(), payload.room_id, { + forceTURN: UserSettingsStore.getLocalSetting('webRtcForceTURN', false), + }); placeCall(call); } else { // > 2 diff --git a/src/TextForEvent.js b/src/TextForEvent.js index fa78f9d61b..de12cec502 100644 --- a/src/TextForEvent.js +++ b/src/TextForEvent.js @@ -13,9 +13,8 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - -var MatrixClientPeg = require("./MatrixClientPeg"); -var CallHandler = require("./CallHandler"); +import MatrixClientPeg from "./MatrixClientPeg"; +import CallHandler from "./CallHandler"; import { _t } from './languageHandler'; import * as Roles from './Roles'; @@ -117,7 +116,7 @@ function textForTopicEvent(ev) { function textForRoomNameEvent(ev) { var senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender(); - + if (!ev.getContent().name || ev.getContent().name.trim().length === 0) { return _t('%(senderDisplayName)s removed the room name.', {senderDisplayName: senderDisplayName}); } @@ -142,9 +141,21 @@ function textForCallAnswerEvent(event) { } function textForCallHangupEvent(event) { - var senderName = event.sender ? event.sender.name : _t('Someone'); - var supported = MatrixClientPeg.get().supportsVoip() ? "" : _t('(not supported by this browser)'); - return _t('%(senderName)s ended the call.', {senderName: senderName}) + ' ' + supported; + const senderName = event.sender ? event.sender.name : _t('Someone'); + const eventContent = event.getContent(); + let reason = ""; + if(!MatrixClientPeg.get().supportsVoip()) { + reason = _t('(not supported by this browser)'); + } else if(eventContent.reason) { + if (eventContent.reason === "ice_failed") { + reason = _t('(could not connect media)'); + } else if (eventContent.reason === "invite_timeout") { + reason = _t('(no answer)'); + } else { + reason = _t('(unknown failure: %(reason)s)', {reason: eventContent.reason}); + } + } + return _t('%(senderName)s ended the call.', {senderName}) + ' ' + reason; } function textForCallInviteEvent(event) { diff --git a/src/async-components/views/dialogs/ExportE2eKeysDialog.js b/src/async-components/views/dialogs/ExportE2eKeysDialog.js index 045ea63c34..8f113353d9 100644 --- a/src/async-components/views/dialogs/ExportE2eKeysDialog.js +++ b/src/async-components/views/dialogs/ExportE2eKeysDialog.js @@ -81,11 +81,13 @@ export default React.createClass({ FileSaver.saveAs(blob, 'riot-keys.txt'); this.props.onFinished(true); }).catch((e) => { + console.error("Error exporting e2e keys:", e); if (this._unmounted) { return; } + const msg = e.friendlyText || _t('Unknown error'); this.setState({ - errStr: e.message, + errStr: msg, phase: PHASE_EDIT, }); }); diff --git a/src/async-components/views/dialogs/ImportE2eKeysDialog.js b/src/async-components/views/dialogs/ImportE2eKeysDialog.js index 91010d33b9..9eac7f78b2 100644 --- a/src/async-components/views/dialogs/ImportE2eKeysDialog.js +++ b/src/async-components/views/dialogs/ImportE2eKeysDialog.js @@ -89,11 +89,13 @@ export default React.createClass({ // TODO: it would probably be nice to give some feedback about what we've imported here. this.props.onFinished(true); }).catch((e) => { + console.error("Error importing e2e keys:", e); if (this._unmounted) { return; } + const msg = e.friendlyText || _t('Unknown error'); this.setState({ - errStr: e.message, + errStr: msg, phase: PHASE_EDIT, }); }); diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index 0444368298..a2a2c30889 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -35,7 +35,7 @@ import * as Rooms from '../../Rooms'; import linkifyMatrix from "../../linkify-matrix"; import * as Lifecycle from '../../Lifecycle'; // LifecycleStore is not used but does listen to and dispatch actions -import LifecycleStore from '../../stores/LifecycleStore'; +require('../../stores/LifecycleStore'); import RoomViewStore from '../../stores/RoomViewStore'; import PageTypes from '../../PageTypes'; @@ -128,11 +128,6 @@ module.exports = React.createClass({ hasNewVersion: false, newVersionReleaseNotes: null, - // The username to default to when upgrading an account from a guest - upgradeUsername: null, - // The access token we had for our guest account, used when upgrading to a normal account - guestAccessToken: null, - // Parameters used in the registration dance with the IS register_client_secret: null, register_session_id: null, @@ -191,7 +186,7 @@ module.exports = React.createClass({ componentWillMount: function() { SdkConfig.put(this.props.config); - RoomViewStore.addListener(this._onRoomViewStoreUpdated); + this._roomViewStoreToken = RoomViewStore.addListener(this._onRoomViewStoreUpdated); this._onRoomViewStoreUpdated(); if (!UserSettingsStore.getLocalSetting('analyticsOptOut', false)) Analytics.enable(); @@ -300,6 +295,7 @@ module.exports = React.createClass({ UDEHandler.stopListening(); window.removeEventListener("focus", this.onFocus); window.removeEventListener('resize', this.handleResize); + this._roomViewStoreToken.remove(); }, componentDidUpdate: function() { @@ -315,8 +311,6 @@ module.exports = React.createClass({ viewUserId: null, loggedIn: false, ready: false, - upgradeUsername: null, - guestAccessToken: null, }; Object.assign(newState, state); this.setState(newState); @@ -351,25 +345,6 @@ module.exports = React.createClass({ screen: 'post_registration', }); break; - case 'start_upgrade_registration': - // also stash our credentials, then if we restore the session, - // we can just do it the same way whether we started upgrade - // registration or explicitly logged out - this.setStateForNewScreen({ - guestCreds: MatrixClientPeg.getCredentials(), - screen: "register", - upgradeUsername: MatrixClientPeg.get().getUserIdLocalpart(), - guestAccessToken: MatrixClientPeg.get().getAccessToken(), - }); - - // stop the client: if we are syncing whilst the registration - // is completed in another browser, we'll be 401ed for using - // a guest access token for a non-guest account. - // It will be restarted in onReturnToGuestClick - Lifecycle.stopMatrixClient(); - - this.notifyNewScreen('register'); - break; case 'start_password_recovery': this.setStateForNewScreen({ screen: 'forgot_password', @@ -745,8 +720,8 @@ module.exports = React.createClass({ title: _t('Create Room'), description: _t('Room name (optional)'), button: _t('Create Room'), - onFinished: (should_create, name) => { - if (should_create) { + onFinished: (shouldCreate, name) => { + if (shouldCreate) { const createOpts = {}; if (name) createOpts.name = name; createRoom({createOpts}).done(); @@ -1401,8 +1376,6 @@ module.exports = React.createClass({ idSid={this.state.register_id_sid} email={this.props.startingFragmentQueryParams.email} referrer={this.props.startingFragmentQueryParams.referrer} - username={this.state.upgradeUsername} - guestAccessToken={this.state.guestAccessToken} defaultHsUrl={this.getDefaultHsUrl()} defaultIsUrl={this.getDefaultIsUrl()} brand={this.props.config.brand} diff --git a/src/components/structures/UserSettings.js b/src/components/structures/UserSettings.js index 9059378d32..5ac2e77256 100644 --- a/src/components/structures/UserSettings.js +++ b/src/components/structures/UserSettings.js @@ -110,6 +110,13 @@ const ANALYTICS_SETTINGS_LABELS = [ }, ]; +const WEBRTC_SETTINGS_LABELS = [ + { + id: 'webRtcForceTURN', + label: 'Disable Peer-to-Peer for 1:1 calls', + }, +]; + // Warning: Each "label" string below must be added to i18n/strings/en_EN.json, // since they will be translated when rendered. const CRYPTO_SETTINGS_LABELS = [ @@ -310,11 +317,6 @@ module.exports = React.createClass({ }, onAvatarPickerClick: function(ev) { - if (MatrixClientPeg.get().isGuest()) { - dis.dispatch({action: 'view_set_mxid'}); - return; - } - if (this.refs.file_label) { this.refs.file_label.click(); } @@ -389,17 +391,14 @@ module.exports = React.createClass({ const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); Modal.createDialog(ErrorDialog, { title: _t("Success"), - description: _t("Your password was successfully changed. You will not receive push notifications on other devices until you log back in to them") + ".", + description: _t( + "Your password was successfully changed. You will not receive " + + "push notifications on other devices until you log back in to them", + ) + ".", }); dis.dispatch({action: 'password_changed'}); }, - onUpgradeClicked: function() { - dis.dispatch({ - action: "start_upgrade_registration", - }); - }, - onEnableNotificationsChange: function(event) { UserSettingsStore.setEnableNotifications(event.target.checked); }, @@ -427,7 +426,10 @@ module.exports = React.createClass({ this._addThreepid.addEmailAddress(emailAddress, true).done(() => { Modal.createDialog(QuestionDialog, { title: _t("Verification Pending"), - description: _t("Please check your email and click on the link it contains. Once this is done, click continue."), + description: _t( + "Please check your email and click on the link it contains. Once this " + + "is done, click continue.", + ), button: _t('Continue'), onFinished: this.onEmailDialogFinished, }); @@ -447,7 +449,7 @@ module.exports = React.createClass({ const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); Modal.createDialog(QuestionDialog, { title: _t("Remove Contact Information?"), - description: _t("Remove %(threePid)s?", { threePid : threepid.address }), + description: _t("Remove %(threePid)s?", { threePid: threepid.address }), button: _t('Remove'), onFinished: (submit) => { if (submit) { @@ -489,8 +491,8 @@ module.exports = React.createClass({ this.setState({email_add_pending: false}); if (err.errcode == 'M_THREEPID_AUTH_FAILED') { const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); - let message = _t("Unable to verify email address.") + " " + - _t("Please check your email and click on the link it contains. Once this is done, click continue."); + const message = _t("Unable to verify email address.") + " " + + _t("Please check your email and click on the link it contains. Once this is done, click continue."); Modal.createDialog(QuestionDialog, { title: _t("Verification Pending"), description: message, @@ -608,7 +610,7 @@ module.exports = React.createClass({ } }, - _renderLanguageSetting: function () { + _renderLanguageSetting: function() { const LanguageDropdown = sdk.getComponent('views.elements.LanguageDropdown'); return
@@ -639,7 +641,7 @@ module.exports = React.createClass({ UserSettingsStore.setUrlPreviewsDisabled(e.target.checked) } + onChange={ this._onPreviewsDisabledChanged } />
; }, + _onPreviewsDisabledChanged: function(e) { + UserSettingsStore.setUrlPreviewsDisabled(e.target.checked); + }, + _renderSyncedSetting: function(setting) { + // TODO: this ought to be a separate component so that we don't need + // to rebind the onChange each time we render + + const onChange = (e) => { + UserSettingsStore.setSyncedSetting(setting.id, e.target.checked); + if (setting.fn) setting.fn(e.target.checked); + }; + return
{ - UserSettingsStore.setSyncedSetting(setting.id, e.target.checked); - if (setting.fn) setting.fn(e.target.checked); - } - } + onChange={ onChange } />