Merge branch 'develop' into luke/ilag-i18n
In order to get ILAG internationalised Conflicts: src/components/structures/LoggedInView.js src/components/structures/MatrixChat.js src/components/views/dialogs/ChatCreateOrReuseDialog.js src/components/views/dialogs/SetDisplayNameDialog.js src/createRoom.js src/i18n/strings/en_EN.json
This commit is contained in:
commit
619830617a
79 changed files with 3924 additions and 581 deletions
|
@ -18,9 +18,11 @@ limitations under the License.
|
|||
import * as Matrix from 'matrix-js-sdk';
|
||||
import React from 'react';
|
||||
|
||||
import UserSettingsStore from '../../UserSettingsStore';
|
||||
import KeyCode from '../../KeyCode';
|
||||
import Notifier from '../../Notifier';
|
||||
import PageTypes from '../../PageTypes';
|
||||
import CallMediaHandler from '../../CallMediaHandler';
|
||||
import sdk from '../../index';
|
||||
import dis from '../../dispatcher';
|
||||
import sessionStore from '../../stores/SessionStore';
|
||||
|
@ -68,6 +70,13 @@ export default React.createClass({
|
|||
};
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
// use compact timeline view
|
||||
useCompactLayout: UserSettingsStore.getSyncedSetting('useCompactLayout'),
|
||||
};
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
// stash the MatrixClient in case we log out before we are unmounted
|
||||
this._matrixClient = this.props.matrixClient;
|
||||
|
@ -76,6 +85,8 @@ export default React.createClass({
|
|||
// RoomView.getScrollState()
|
||||
this._scrollStateMap = {};
|
||||
|
||||
CallMediaHandler.loadDevices();
|
||||
|
||||
document.addEventListener('keydown', this._onKeyDown);
|
||||
|
||||
this._sessionStore = sessionStore;
|
||||
|
@ -83,10 +94,13 @@ export default React.createClass({
|
|||
this._setStateFromSessionStore,
|
||||
);
|
||||
this._setStateFromSessionStore();
|
||||
|
||||
this._matrixClient.on("accountData", this.onAccountData);
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
document.removeEventListener('keydown', this._onKeyDown);
|
||||
this._matrixClient.removeListener("accountData", this.onAccountData);
|
||||
if (this._sessionStoreToken) {
|
||||
this._sessionStoreToken.remove();
|
||||
}
|
||||
|
@ -119,6 +133,14 @@ export default React.createClass({
|
|||
});
|
||||
},
|
||||
|
||||
onAccountData: function(event) {
|
||||
if (event.getType() === "im.vector.web.settings") {
|
||||
this.setState({
|
||||
useCompactLayout: event.getContent().useCompactLayout,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
_onKeyDown: function(ev) {
|
||||
/*
|
||||
// Remove this for now as ctrl+alt = alt-gr so this breaks keyboards which rely on alt-gr for numbers
|
||||
|
@ -279,6 +301,9 @@ export default React.createClass({
|
|||
if (topBar) {
|
||||
bodyClasses += ' mx_MatrixChat_toolbarShowing';
|
||||
}
|
||||
if (this.state.useCompactLayout) {
|
||||
bodyClasses += ' mx_MatrixChat_useCompactLayout';
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='mx_MatrixChat_wrapper'>
|
||||
|
|
|
@ -41,7 +41,7 @@ import PageTypes from '../../PageTypes';
|
|||
|
||||
import createRoom from "../../createRoom";
|
||||
import * as UDEHandler from '../../UnknownDeviceErrorHandler';
|
||||
import { _t } from '../../languageHandler';
|
||||
import { _t, getCurrentLanguage } from '../../languageHandler';
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'MatrixChat',
|
||||
|
@ -404,7 +404,7 @@ module.exports = React.createClass({
|
|||
}, (err) => {
|
||||
modal.close();
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: _t('Failed to reject invitation'),
|
||||
title: _t('Failed to reject invitation'),
|
||||
description: err.toString(),
|
||||
});
|
||||
});
|
||||
|
@ -465,8 +465,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();
|
||||
|
@ -613,39 +613,44 @@ module.exports = React.createClass({
|
|||
|
||||
// switch view to the given room
|
||||
//
|
||||
// @param {Object} room_info Object containing data about the room to be joined
|
||||
// @param {string=} room_info.room_id ID of the room to join. One of room_id or room_alias must be given.
|
||||
// @param {string=} room_info.room_alias Alias of the room to join. One of room_id or room_alias must be given.
|
||||
// @param {boolean=} room_info.auto_join If true, automatically attempt to join the room if not already a member.
|
||||
// @param {boolean=} room_info.show_settings Makes RoomView show the room settings dialog.
|
||||
// @param {string=} room_info.event_id ID of the event in this room to show: this will cause a switch to the
|
||||
// @param {Object} roomInfo Object containing data about the room to be joined
|
||||
// @param {string=} roomInfo.room_id ID of the room to join. One of room_id or room_alias must be given.
|
||||
// @param {string=} roomInfo.room_alias Alias of the room to join. One of room_id or room_alias must be given.
|
||||
// @param {boolean=} roomInfo.auto_join If true, automatically attempt to join the room if not already a member.
|
||||
// @param {boolean=} roomInfo.show_settings Makes RoomView show the room settings dialog.
|
||||
// @param {string=} roomInfo.event_id ID of the event in this room to show: this will cause a switch to the
|
||||
// context of that particular event.
|
||||
// @param {Object=} room_info.third_party_invite Object containing data about the third party
|
||||
// @param {Object=} roomInfo.third_party_invite Object containing data about the third party
|
||||
// we received to join the room, if any.
|
||||
// @param {string=} room_info.third_party_invite.inviteSignUrl 3pid invite sign URL
|
||||
// @param {string=} room_info.third_party_invite.invitedEmail The email address the invite was sent to
|
||||
// @param {Object=} room_info.oob_data Object of additional data about the room
|
||||
// @param {string=} roomInfo.third_party_invite.inviteSignUrl 3pid invite sign URL
|
||||
// @param {string=} roomInfo.third_party_invite.invitedEmail The email address the invite was sent to
|
||||
// @param {Object=} roomInfo.oob_data Object of additional data about the room
|
||||
// that has been passed out-of-band (eg.
|
||||
// room name and avatar from an invite email)
|
||||
_viewRoom: function(room_info) {
|
||||
_viewRoom: function(roomInfo) {
|
||||
this.focusComposer = true;
|
||||
|
||||
const newState = {
|
||||
initialEventId: room_info.event_id,
|
||||
highlightedEventId: room_info.event_id,
|
||||
initialEventId: roomInfo.event_id,
|
||||
highlightedEventId: roomInfo.event_id,
|
||||
initialEventPixelOffset: undefined,
|
||||
page_type: PageTypes.RoomView,
|
||||
thirdPartyInvite: room_info.third_party_invite,
|
||||
roomOobData: room_info.oob_data,
|
||||
autoJoin: room_info.auto_join,
|
||||
thirdPartyInvite: roomInfo.third_party_invite,
|
||||
roomOobData: roomInfo.oob_data,
|
||||
currentRoomAlias: roomInfo.room_alias,
|
||||
autoJoin: roomInfo.auto_join,
|
||||
};
|
||||
|
||||
if (!roomInfo.room_alias) {
|
||||
newState.currentRoomId = roomInfo.room_id;
|
||||
}
|
||||
|
||||
// if we aren't given an explicit event id, look for one in the
|
||||
// scrollStateMap.
|
||||
//
|
||||
// TODO: do this in RoomView rather than here
|
||||
if (!room_info.event_id && this.refs.loggedInView) {
|
||||
const scrollState = this.refs.loggedInView.getScrollStateForRoom(room_info.room_id);
|
||||
if (!roomInfo.event_id && this.refs.loggedInView) {
|
||||
const scrollState = this.refs.loggedInView.getScrollStateForRoom(roomInfo.room_id);
|
||||
if (scrollState) {
|
||||
newState.initialEventId = scrollState.focussedEvent;
|
||||
newState.initialEventPixelOffset = scrollState.pixelOffset;
|
||||
|
@ -657,15 +662,15 @@ module.exports = React.createClass({
|
|||
let waitFor = q(null);
|
||||
if (!this.firstSyncComplete) {
|
||||
if (!this.firstSyncPromise) {
|
||||
console.warn('Cannot view a room before first sync. room_id:', room_info.room_id);
|
||||
console.warn('Cannot view a room before first sync. room_id:', roomInfo.room_id);
|
||||
return;
|
||||
}
|
||||
waitFor = this.firstSyncPromise.promise;
|
||||
}
|
||||
|
||||
waitFor.done(() => {
|
||||
let presentedId = room_info.room_alias || room_info.room_id;
|
||||
const room = MatrixClientPeg.get().getRoom(room_info.room_id);
|
||||
let presentedId = roomInfo.room_alias || roomInfo.room_id;
|
||||
const room = MatrixClientPeg.get().getRoom(roomInfo.room_id);
|
||||
if (room) {
|
||||
const theAlias = Rooms.getDisplayAliasForRoom(room);
|
||||
if (theAlias) presentedId = theAlias;
|
||||
|
@ -677,8 +682,8 @@ module.exports = React.createClass({
|
|||
}
|
||||
}
|
||||
|
||||
if (room_info.event_id) {
|
||||
presentedId += "/" + room_info.event_id;
|
||||
if (roomInfo.event_id) {
|
||||
presentedId += "/" + roomInfo.event_id;
|
||||
}
|
||||
this.notifyNewScreen('room/' + presentedId);
|
||||
newState.ready = true;
|
||||
|
@ -723,7 +728,7 @@ module.exports = React.createClass({
|
|||
title: _t('Start a chat'),
|
||||
description: _t("Who would you like to communicate with?"),
|
||||
placeholder: _t("Email, name or matrix ID"),
|
||||
button: _t("Start Chat")
|
||||
button: _t("Start Chat"),
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -793,8 +798,12 @@ module.exports = React.createClass({
|
|||
|
||||
const roomToLeave = MatrixClientPeg.get().getRoom(roomId);
|
||||
Modal.createDialog(QuestionDialog, {
|
||||
title: "Leave room",
|
||||
description: <span>Are you sure you want to leave the room <i>{roomToLeave.name}</i>?</span>,
|
||||
title: _t("Leave room"),
|
||||
description: (
|
||||
<span>
|
||||
{_t("Are you sure you want to leave the room '%(roomName)s'?", {roomName: roomToLeave.name})}
|
||||
</span>
|
||||
),
|
||||
onFinished: (shouldLeave) => {
|
||||
if (shouldLeave) {
|
||||
const d = MatrixClientPeg.get().leave(roomId);
|
||||
|
@ -907,7 +916,7 @@ module.exports = React.createClass({
|
|||
MatrixClientPeg.get().getUserIdLocalpart(),
|
||||
);
|
||||
|
||||
if (this.props.config.welcomeUserId) {
|
||||
if (this.props.config.welcomeUserId && getCurrentLanguage().startsWith("en")) {
|
||||
createRoom({
|
||||
dmUserId: this.props.config.welcomeUserId,
|
||||
// Only view the welcome user if we're NOT looking at a room
|
||||
|
|
|
@ -90,6 +90,9 @@ module.exports = React.createClass({
|
|||
|
||||
// show timestamps always
|
||||
alwaysShowTimestamps: React.PropTypes.bool,
|
||||
|
||||
// hide redacted events as per old behaviour
|
||||
hideRedactions: React.PropTypes.bool,
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
|
@ -316,7 +319,7 @@ module.exports = React.createClass({
|
|||
const key = "membereventlistsummary-" + (prevEvent ? mxEv.getId() : "initial");
|
||||
|
||||
if (this._wantsDateSeparator(prevEvent, mxEv.getDate())) {
|
||||
let dateSeparator = <li key={ts1+'~'}><DateSeparator key={ts1+'~'} ts={ts1}/></li>;
|
||||
let dateSeparator = <li key={ts1+'~'}><DateSeparator key={ts1+'~'} ts={ts1} showTwelveHour={this.props.isTwelveHour}/></li>;
|
||||
ret.push(dateSeparator);
|
||||
}
|
||||
|
||||
|
@ -458,11 +461,13 @@ module.exports = React.createClass({
|
|||
|
||||
// do we need a date separator since the last event?
|
||||
if (this._wantsDateSeparator(prevEvent, eventDate)) {
|
||||
var dateSeparator = <li key={ts1}><DateSeparator key={ts1} ts={ts1}/></li>;
|
||||
var dateSeparator = <li key={ts1}><DateSeparator key={ts1} ts={ts1} showTwelveHour={this.props.isTwelveHour}/></li>;
|
||||
ret.push(dateSeparator);
|
||||
continuation = false;
|
||||
}
|
||||
|
||||
if (mxEv.isRedacted() && this.props.hideRedactions) return ret;
|
||||
|
||||
var eventId = mxEv.getId();
|
||||
var highlight = (eventId == this.props.highlightedEventId);
|
||||
|
||||
|
|
|
@ -131,6 +131,8 @@ var TimelinePanel = React.createClass({
|
|||
}
|
||||
}
|
||||
|
||||
const syncedSettings = UserSettingsStore.getSyncedSettings();
|
||||
|
||||
return {
|
||||
events: [],
|
||||
timelineLoading: true, // track whether our room timeline is loading
|
||||
|
@ -175,10 +177,13 @@ var TimelinePanel = React.createClass({
|
|||
clientSyncState: MatrixClientPeg.get().getSyncState(),
|
||||
|
||||
// should the event tiles have twelve hour times
|
||||
isTwelveHour: UserSettingsStore.getSyncedSetting('showTwelveHourTimestamps'),
|
||||
isTwelveHour: syncedSettings.showTwelveHourTimestamps,
|
||||
|
||||
// always show timestamps on event tiles?
|
||||
alwaysShowTimestamps: UserSettingsStore.getSyncedSetting('alwaysShowTimestamps'),
|
||||
alwaysShowTimestamps: syncedSettings.alwaysShowTimestamps,
|
||||
|
||||
// hide redacted events as per old behaviour
|
||||
hideRedactions: syncedSettings.hideRedactions,
|
||||
};
|
||||
},
|
||||
|
||||
|
@ -915,7 +920,7 @@ var TimelinePanel = React.createClass({
|
|||
});
|
||||
};
|
||||
}
|
||||
var message = (error.errcode == 'M_FORBIDDEN')
|
||||
var message = (error.errcode == 'M_FORBIDDEN')
|
||||
? _t("Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question") + "."
|
||||
: _t("Tried to load a specific point in this room's timeline, but was unable to find it") + ".";
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
|
@ -1113,26 +1118,27 @@ var TimelinePanel = React.createClass({
|
|||
);
|
||||
return (
|
||||
<MessagePanel ref="messagePanel"
|
||||
hidden={ this.props.hidden }
|
||||
backPaginating={ this.state.backPaginating }
|
||||
forwardPaginating={ forwardPaginating }
|
||||
events={ this.state.events }
|
||||
highlightedEventId={ this.props.highlightedEventId }
|
||||
readMarkerEventId={ this.state.readMarkerEventId }
|
||||
readMarkerVisible={ this.state.readMarkerVisible }
|
||||
suppressFirstDateSeparator={ this.state.canBackPaginate }
|
||||
showUrlPreview = { this.props.showUrlPreview }
|
||||
manageReadReceipts = { this.props.manageReadReceipts }
|
||||
ourUserId={ MatrixClientPeg.get().credentials.userId }
|
||||
stickyBottom={ stickyBottom }
|
||||
onScroll={ this.onMessageListScroll }
|
||||
onFillRequest={ this.onMessageListFillRequest }
|
||||
onUnfillRequest={ this.onMessageListUnfillRequest }
|
||||
opacity={ this.props.opacity }
|
||||
isTwelveHour={ this.state.isTwelveHour }
|
||||
alwaysShowTimestamps={ this.state.alwaysShowTimestamps }
|
||||
className={ this.props.className }
|
||||
tileShape={ this.props.tileShape }
|
||||
hidden={ this.props.hidden }
|
||||
hideRedactions={ this.state.hideRedactions }
|
||||
backPaginating={ this.state.backPaginating }
|
||||
forwardPaginating={ forwardPaginating }
|
||||
events={ this.state.events }
|
||||
highlightedEventId={ this.props.highlightedEventId }
|
||||
readMarkerEventId={ this.state.readMarkerEventId }
|
||||
readMarkerVisible={ this.state.readMarkerVisible }
|
||||
suppressFirstDateSeparator={ this.state.canBackPaginate }
|
||||
showUrlPreview = { this.props.showUrlPreview }
|
||||
manageReadReceipts = { this.props.manageReadReceipts }
|
||||
ourUserId={ MatrixClientPeg.get().credentials.userId }
|
||||
stickyBottom={ stickyBottom }
|
||||
onScroll={ this.onMessageListScroll }
|
||||
onFillRequest={ this.onMessageListFillRequest }
|
||||
onUnfillRequest={ this.onMessageListUnfillRequest }
|
||||
opacity={ this.props.opacity }
|
||||
isTwelveHour={ this.state.isTwelveHour }
|
||||
alwaysShowTimestamps={ this.state.alwaysShowTimestamps }
|
||||
className={ this.props.className }
|
||||
tileShape={ this.props.tileShape }
|
||||
/>
|
||||
);
|
||||
},
|
||||
|
|
|
@ -24,6 +24,7 @@ const dis = require("../../dispatcher");
|
|||
const q = require('q');
|
||||
const packageJson = require('../../../package.json');
|
||||
const UserSettingsStore = require('../../UserSettingsStore');
|
||||
const CallMediaHandler = require('../../CallMediaHandler');
|
||||
const GeminiScrollbar = require('react-gemini-scrollbar');
|
||||
const Email = require('../../email');
|
||||
const AddThreepid = require('../../AddThreepid');
|
||||
|
@ -79,11 +80,15 @@ const SETTINGS_LABELS = [
|
|||
id: 'showTwelveHourTimestamps',
|
||||
label: 'Show timestamps in 12 hour format (e.g. 2:30pm)',
|
||||
},
|
||||
/*
|
||||
{
|
||||
id: 'useCompactLayout',
|
||||
label: 'Use compact timeline layout',
|
||||
},
|
||||
{
|
||||
id: 'hideRedactions',
|
||||
label: 'Hide removed messages',
|
||||
},
|
||||
/*
|
||||
{
|
||||
id: 'useFixedWidthFont',
|
||||
label: 'Use fixed width font',
|
||||
|
@ -176,6 +181,7 @@ module.exports = React.createClass({
|
|||
email_add_pending: false,
|
||||
vectorVersion: undefined,
|
||||
rejectingInvites: false,
|
||||
mediaDevices: null,
|
||||
};
|
||||
},
|
||||
|
||||
|
@ -196,6 +202,8 @@ module.exports = React.createClass({
|
|||
});
|
||||
}
|
||||
|
||||
this._refreshMediaDevices();
|
||||
|
||||
// Bulk rejecting invites:
|
||||
// /sync won't have had time to return when UserSettings re-renders from state changes, so getRooms()
|
||||
// will still return rooms with invites. To get around this, add a listener for
|
||||
|
@ -217,6 +225,13 @@ module.exports = React.createClass({
|
|||
|
||||
this._localSettings = UserSettingsStore.getLocalSettings();
|
||||
|
||||
if (PlatformPeg.get().isElectron()) {
|
||||
const {ipcRenderer} = require('electron');
|
||||
|
||||
ipcRenderer.on('settings', this._electronSettings);
|
||||
ipcRenderer.send('settings_get');
|
||||
}
|
||||
|
||||
this.setState({
|
||||
language: languageHandler.getCurrentLanguage(),
|
||||
});
|
||||
|
@ -239,6 +254,29 @@ module.exports = React.createClass({
|
|||
if (cli) {
|
||||
cli.removeListener("RoomMember.membership", this._onInviteStateChange);
|
||||
}
|
||||
|
||||
if (PlatformPeg.get().isElectron()) {
|
||||
const {ipcRenderer} = require('electron');
|
||||
ipcRenderer.removeListener('settings', this._electronSettings);
|
||||
}
|
||||
},
|
||||
|
||||
_electronSettings: function(ev, settings) {
|
||||
this.setState({ electron_settings: settings });
|
||||
},
|
||||
|
||||
_refreshMediaDevices: function() {
|
||||
q().then(() => {
|
||||
return CallMediaHandler.getDevices();
|
||||
}).then((mediaDevices) => {
|
||||
// console.log("got mediaDevices", mediaDevices, this._unmounted);
|
||||
if (this._unmounted) return;
|
||||
this.setState({
|
||||
mediaDevices,
|
||||
activeAudioInput: this._localSettings['webrtc_audioinput'],
|
||||
activeVideoInput: this._localSettings['webrtc_videoinput'],
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
_refreshFromServer: function() {
|
||||
|
@ -678,8 +716,8 @@ module.exports = React.createClass({
|
|||
<h3>{ _t("Cryptography") }</h3>
|
||||
<div className="mx_UserSettings_section mx_UserSettings_cryptoSection">
|
||||
<ul>
|
||||
<li><label>Device ID:</label> <span><code>{deviceId}</code></span></li>
|
||||
<li><label>Device key:</label> <span><code><b>{identityKey}</b></code></span></li>
|
||||
<li><label>{_t("Device ID:")}</label> <span><code>{deviceId}</code></span></li>
|
||||
<li><label>{_t("Device key:")}</label> <span><code><b>{identityKey}</b></code></span></li>
|
||||
</ul>
|
||||
{ importExportButtons }
|
||||
</div>
|
||||
|
@ -736,6 +774,8 @@ module.exports = React.createClass({
|
|||
},
|
||||
|
||||
_renderAnalyticsControl: function() {
|
||||
if (!SdkConfig.get().piwik) return <div/>;
|
||||
|
||||
return <div>
|
||||
<h3>{ _t('Analytics') }</h3>
|
||||
<div className="mx_UserSettings_section">
|
||||
|
@ -748,6 +788,7 @@ module.exports = React.createClass({
|
|||
_renderLabs: function() {
|
||||
// default to enabled if undefined
|
||||
if (this.props.enableLabs === false) return null;
|
||||
UserSettingsStore.doTranslations();
|
||||
|
||||
const features = UserSettingsStore.LABS_FEATURES.map((feature) => (
|
||||
<div key={feature.id} className="mx_UserSettings_toggle">
|
||||
|
@ -836,6 +877,133 @@ module.exports = React.createClass({
|
|||
</div>;
|
||||
},
|
||||
|
||||
_renderElectronSettings: function() {
|
||||
const settings = this.state.electron_settings;
|
||||
if (!settings) return;
|
||||
|
||||
const {ipcRenderer} = require('electron');
|
||||
|
||||
return <div>
|
||||
<h3>{ _t('Desktop specific') }</h3>
|
||||
<div className="mx_UserSettings_section">
|
||||
<div className="mx_UserSettings_toggle">
|
||||
<input type="checkbox"
|
||||
name="auto-launch"
|
||||
defaultChecked={settings['auto-launch']}
|
||||
onChange={(e) => {
|
||||
ipcRenderer.send('settings_set', 'auto-launch', e.target.checked);
|
||||
}}
|
||||
/>
|
||||
<label htmlFor="auto-launch">{_t('Start automatically after system login')}</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>;
|
||||
},
|
||||
|
||||
_mapWebRtcDevicesToSpans: function(devices) {
|
||||
return devices.map((device) => <span key={device.deviceId}>{device.label}</span>);
|
||||
},
|
||||
|
||||
_setAudioInput: function(deviceId) {
|
||||
this.setState({activeAudioInput: deviceId});
|
||||
CallMediaHandler.setAudioInput(deviceId);
|
||||
},
|
||||
|
||||
_setVideoInput: function(deviceId) {
|
||||
this.setState({activeVideoInput: deviceId});
|
||||
CallMediaHandler.setVideoInput(deviceId);
|
||||
},
|
||||
|
||||
_requestMediaPermissions: function(event) {
|
||||
const getUserMedia = (
|
||||
window.navigator.getUserMedia || window.navigator.webkitGetUserMedia || window.navigator.mozGetUserMedia
|
||||
);
|
||||
if (getUserMedia) {
|
||||
return getUserMedia.apply(window.navigator, [
|
||||
{ video: true, audio: true },
|
||||
this._refreshMediaDevices,
|
||||
function() {
|
||||
const ErrorDialog = sdk.getComponent('dialogs.ErrorDialog');
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: _t('No media permissions'),
|
||||
description: _t('You may need to manually permit Riot to access your microphone/webcam'),
|
||||
});
|
||||
},
|
||||
]);
|
||||
}
|
||||
},
|
||||
|
||||
_renderWebRtcSettings: function() {
|
||||
if (this.state.mediaDevices === false) {
|
||||
return <div>
|
||||
<h3>{_t('VoIP')}</h3>
|
||||
<div className="mx_UserSettings_section">
|
||||
<p className="mx_UserSettings_link" onClick={this._requestMediaPermissions}>
|
||||
{_t('Missing Media Permissions, click here to request.')}
|
||||
</p>
|
||||
</div>
|
||||
</div>;
|
||||
} else if (!this.state.mediaDevices) return;
|
||||
|
||||
const Dropdown = sdk.getComponent('elements.Dropdown');
|
||||
|
||||
let microphoneDropdown = <p>{_t('No Microphones detected')}</p>;
|
||||
let webcamDropdown = <p>{_t('No Webcams detected')}</p>;
|
||||
|
||||
const defaultOption = {
|
||||
deviceId: '',
|
||||
label: _t('Default Device'),
|
||||
};
|
||||
|
||||
const audioInputs = this.state.mediaDevices.audioinput.slice(0);
|
||||
if (audioInputs.length > 0) {
|
||||
let defaultInput = '';
|
||||
if (!audioInputs.some((input) => input.deviceId === 'default')) {
|
||||
audioInputs.unshift(defaultOption);
|
||||
} else {
|
||||
defaultInput = 'default';
|
||||
}
|
||||
|
||||
microphoneDropdown = <div>
|
||||
<h4>{_t('Microphone')}</h4>
|
||||
<Dropdown
|
||||
className="mx_UserSettings_webRtcDevices_dropdown"
|
||||
value={this.state.activeAudioInput || defaultInput}
|
||||
onOptionChange={this._setAudioInput}>
|
||||
{this._mapWebRtcDevicesToSpans(audioInputs)}
|
||||
</Dropdown>
|
||||
</div>;
|
||||
}
|
||||
|
||||
const videoInputs = this.state.mediaDevices.videoinput.slice(0);
|
||||
if (videoInputs.length > 0) {
|
||||
let defaultInput = '';
|
||||
if (!videoInputs.some((input) => input.deviceId === 'default')) {
|
||||
videoInputs.unshift(defaultOption);
|
||||
} else {
|
||||
defaultInput = 'default';
|
||||
}
|
||||
|
||||
webcamDropdown = <div>
|
||||
<h4>{_t('Camera')}</h4>
|
||||
<Dropdown
|
||||
className="mx_UserSettings_webRtcDevices_dropdown"
|
||||
value={this.state.activeVideoInput || defaultInput}
|
||||
onOptionChange={this._setVideoInput}>
|
||||
{this._mapWebRtcDevicesToSpans(videoInputs)}
|
||||
</Dropdown>
|
||||
</div>;
|
||||
}
|
||||
|
||||
return <div>
|
||||
<h3>{_t('VoIP')}</h3>
|
||||
<div className="mx_UserSettings_section">
|
||||
{microphoneDropdown}
|
||||
{webcamDropdown}
|
||||
</div>
|
||||
</div>;
|
||||
},
|
||||
|
||||
_showSpoiler: function(event) {
|
||||
const target = event.target;
|
||||
target.innerHTML = target.getAttribute('data-spoiler');
|
||||
|
@ -1033,11 +1201,14 @@ module.exports = React.createClass({
|
|||
|
||||
{this._renderUserInterfaceSettings()}
|
||||
{this._renderLabs()}
|
||||
{this._renderWebRtcSettings()}
|
||||
{this._renderDevicesPanel()}
|
||||
{this._renderCryptoInfo()}
|
||||
{this._renderBulkOptions()}
|
||||
{this._renderBugReport()}
|
||||
|
||||
{PlatformPeg.get().isElectron() && this._renderElectronSettings()}
|
||||
|
||||
{this._renderAnalyticsControl()}
|
||||
|
||||
<h3>{ _t("Advanced") }</h3>
|
||||
|
|
|
@ -229,7 +229,7 @@ module.exports = React.createClass({
|
|||
<div className="mx_Login_error">
|
||||
</div>
|
||||
<a className="mx_Login_create" onClick={this.props.onLoginClick} href="#">
|
||||
Return to login
|
||||
{_t('Return to login screen')}
|
||||
</a>
|
||||
<a className="mx_Login_create" onClick={this.props.onRegisterClick} href="#">
|
||||
{ _t('Create an account') }
|
||||
|
|
|
@ -18,7 +18,7 @@ limitations under the License.
|
|||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import { _t, _tJsx } from '../../../languageHandler';
|
||||
import ReactDOM from 'react-dom';
|
||||
import sdk from '../../../index';
|
||||
import Login from '../../../Login';
|
||||
|
@ -222,17 +222,20 @@ module.exports = React.createClass({
|
|||
(this.state.enteredHomeserverUrl.startsWith("http:") ||
|
||||
!this.state.enteredHomeserverUrl.startsWith("http")))
|
||||
{
|
||||
const urlStart = <a href='https://www.google.com/search?&q=enable%20unsafe%20scripts'>;
|
||||
const urlEnd = </a>;
|
||||
errorText = <span>
|
||||
{ _t('Can\'t connect to homeserver via HTTP when an HTTPS URL is in your browser bar. Either use HTTPS or %(urlStart)s enable unsafe scripts %(urlEnd)s', {urlStart: urlStart, urlEnd: urlEnd})}
|
||||
{ _tJsx("Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. " +
|
||||
"Either use HTTPS or <a>enable unsafe scripts</a>.",
|
||||
/<a>(.*?)<\/a>/,
|
||||
(sub) => { return <a href="https://www.google.com/search?&q=enable%20unsafe%20scripts">{ sub }</a>; }
|
||||
)}
|
||||
</span>;
|
||||
}
|
||||
else {
|
||||
const urlStart = <a href={this.state.enteredHomeserverUrl}>;
|
||||
const urlEnd = </a>;
|
||||
errorText = <span>
|
||||
{ _t('Can\'t connect to homeserver - please check your connectivity and ensure your %(urlStart)s homeserver\'s SSL certificate %(urlEnd)s is trusted', {urlStart: urlStart, urlEnd: urlEnd})}
|
||||
{ _tJsx("Can't connect to homeserver - please check your connectivity and ensure your <a>homeserver's SSL certificate</a> is trusted.",
|
||||
/<a>(.*?)<\/a>/,
|
||||
(sub) => { return <a href={this.state.enteredHomeserverUrl}>{ sub }</a>; }
|
||||
)}
|
||||
</span>;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -266,7 +266,7 @@ module.exports = React.createClass({
|
|||
errMsg = _t('Passwords don\'t match.');
|
||||
break;
|
||||
case "RegistrationForm.ERR_PASSWORD_LENGTH":
|
||||
errMsg = _t('Password too short (min %(MIN_PASSWORD_LENGTH)s).', {MIN_PASSWORD_LENGTH: $MIN_PASSWORD_LENGTH})
|
||||
errMsg = _t('Password too short (min %(MIN_PASSWORD_LENGTH)s).', {MIN_PASSWORD_LENGTH: MIN_PASSWORD_LENGTH});
|
||||
break;
|
||||
case "RegistrationForm.ERR_EMAIL_INVALID":
|
||||
errMsg = _t('This doesn\'t look like a valid email address.');
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue