diff --git a/src/components/structures/FilePanel.js b/src/components/structures/FilePanel.js index 6696f3cc0f..23feb4cf30 100644 --- a/src/components/structures/FilePanel.js +++ b/src/components/structures/FilePanel.js @@ -116,7 +116,6 @@ const FilePanel = React.createClass({ timelineSet={this.state.timelineSet} showUrlPreview = {false} tileShape="file_grid" - opacity={this.props.opacity} empty={_t('There are no visible files in this room')} /> ); diff --git a/src/components/structures/GroupView.js b/src/components/structures/GroupView.js index 52680ea5fa..9d87f84aef 100644 --- a/src/components/structures/GroupView.js +++ b/src/components/structures/GroupView.js @@ -482,8 +482,8 @@ export default React.createClass({ profileForm: Object.assign({}, this.state.summary.profile), }); dis.dispatch({ - action: 'ui_opacity', - sideOpacity: 0.3, + action: 'panel_disable', + sideDisabled: true, }); }, @@ -492,7 +492,7 @@ export default React.createClass({ editing: false, profileForm: null, }); - dis.dispatch({action: 'ui_opacity'}); + dis.dispatch({action: 'panel_disable'}); }, _onNameChange: function(value) { @@ -549,7 +549,7 @@ export default React.createClass({ editing: false, summary: null, }); - dis.dispatch({action: 'ui_opacity'}); + dis.dispatch({action: 'panel_disable'}); this._initGroupStore(this.props.groupId); }).catch((e) => { this.setState({ @@ -787,8 +787,8 @@ export default React.createClass({ ; } else if (group.myMembership === 'join' && this.state.editing) { const leaveButtonTooltip = this.state.isUserPrivileged ? - _t("You are a member of this community") : - _t("You are an administrator of this community"); + _t("You are an administrator of this community") : + _t("You are a member of this community"); const leaveButtonClasses = classnames({ "mx_RoomHeader_textButton": true, "mx_GroupView_textButton": true, @@ -964,13 +964,15 @@ export default React.createClass({ , ); } else { - rightButtons.push( - - - , - ); + if (summary.user && summary.user.membership === 'join') { + rightButtons.push( + + + , + ); + } if (this.props.collapsedRhs) { rightButtons.push( ; - if (!this.props.collapseRhs) right_panel = ; + if (!this.props.collapseRhs) { + right_panel = ; + } break; case PageTypes.UserSettings: @@ -254,7 +256,7 @@ export default React.createClass({ referralBaseUrl={this.props.config.referralBaseUrl} teamToken={this.props.teamToken} />; - if (!this.props.collapseRhs) right_panel = ; + if (!this.props.collapseRhs) right_panel = ; break; case PageTypes.MyGroups: @@ -266,7 +268,7 @@ export default React.createClass({ onRoomCreated={this.props.onRoomCreated} collapsedRhs={this.props.collapseRhs} />; - if (!this.props.collapseRhs) right_panel = ; + if (!this.props.collapseRhs) right_panel = ; break; case PageTypes.RoomDirectory: @@ -294,14 +296,14 @@ export default React.createClass({ case PageTypes.UserView: page_element = null; // deliberately null for now - right_panel = ; + right_panel = ; break; case PageTypes.GroupView: page_element = ; - if (!this.props.collapseRhs) right_panel = ; + if (!this.props.collapseRhs) right_panel = ; break; } @@ -334,7 +336,7 @@ export default React.createClass({
{ page_element } diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index 3fa628b8a3..e679276a08 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -145,9 +145,9 @@ module.exports = React.createClass({ collapseLhs: false, collapseRhs: false, - leftOpacity: 1.0, - middleOpacity: 1.0, - rightOpacity: 1.0, + leftDisabled: false, + middleDisabled: false, + rightDisabled: false, version: null, newVersion: null, @@ -534,12 +534,11 @@ module.exports = React.createClass({ collapseRhs: false, }); break; - case 'ui_opacity': { - const sideDefault = payload.sideOpacity >= 0.0 ? payload.sideOpacity : 1.0; + case 'panel_disable': { this.setState({ - leftOpacity: payload.leftOpacity >= 0.0 ? payload.leftOpacity : sideDefault, - middleOpacity: payload.middleOpacity || 1.0, - rightOpacity: payload.rightOpacity >= 0.0 ? payload.rightOpacity : sideDefault, + leftDisabled: payload.leftDisabled || payload.sideDisabled || false, + middleDisabled: payload.middleDisabled || false, + rightDisabled: payload.rightDisabled || payload.sideDisabled || false, }); break; } case 'set_theme': diff --git a/src/components/structures/MessagePanel.js b/src/components/structures/MessagePanel.js index 5ce36b4b82..2331e096c0 100644 --- a/src/components/structures/MessagePanel.js +++ b/src/components/structures/MessagePanel.js @@ -16,6 +16,7 @@ limitations under the License. import React from 'react'; import ReactDOM from 'react-dom'; +import classNames from 'classnames'; import UserSettingsStore from '../../UserSettingsStore'; import shouldHideEvent from '../../shouldHideEvent'; import dis from "../../dispatcher"; @@ -78,9 +79,6 @@ module.exports = React.createClass({ // callback which is called when more content is needed. onFillRequest: React.PropTypes.func, - // opacity for dynamic UI fading effects - opacity: React.PropTypes.number, - // className for the panel className: React.PropTypes.string.isRequired, @@ -353,7 +351,7 @@ module.exports = React.createClass({ } if (!isMembershipChange(collapsedMxEv) || - this._wantsDateSeparator(this.props.events[i], collapsedMxEv.getDate())) { + this._wantsDateSeparator(mxEv, collapsedMxEv.getDate())) { break; } @@ -376,9 +374,7 @@ module.exports = React.createClass({ // of MemberEventListSummary, render each member event as if the previous // one was itself. This way, the timestamp of the previous event === the // timestamp of the current event, and no DateSeperator is inserted. - const ret = this._getTilesForEvent(e, e, e === lastShownEvent); - prevEvent = e; - return ret; + return this._getTilesForEvent(e, e, e === lastShownEvent); }).reduce((a, b) => a.concat(b)); if (eventTiles.length === 0) { @@ -397,6 +393,7 @@ module.exports = React.createClass({ ret.push(this._getReadMarkerTile(visible)); } + prevEvent = mxEv; continue; } @@ -649,12 +646,13 @@ module.exports = React.createClass({ } const style = this.props.hidden ? { display: 'none' } : {}; - style.opacity = this.props.opacity; - let className = this.props.className + " mx_fadable"; - if (this.props.alwaysShowTimestamps) { - className += " mx_MessagePanel_alwaysShowTimestamps"; - } + const className = classNames( + this.props.className, + { + "mx_MessagePanel_alwaysShowTimestamps": this.props.alwaysShowTimestamps, + }, + ); return ( diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index 83ca987276..9b6dbb4c27 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -281,7 +281,7 @@ module.exports = React.createClass({ this.setState({ isPeeking: false, }); - + // This won't necessarily be a MatrixError, but we duck-type // here and say if it's got an 'errcode' key with the right value, // it means we can't peek. @@ -1697,7 +1697,7 @@ module.exports = React.createClass({ onResize={this.onChildResize} uploadFile={this.uploadFile} callState={this.state.callState} - opacity={this.props.opacity} + disabled={this.props.disabled} showApps={this.state.showApps} />; } @@ -1758,7 +1758,6 @@ module.exports = React.createClass({ className="mx_RoomView_messagePanel mx_RoomView_searchResultsPanel" onFillRequest={this.onSearchResultsFillRequest} onResize={this.onSearchResultsResize} - style={{ opacity: this.props.opacity }} >
  • { this.getSearchResultTiles() } @@ -1789,7 +1788,6 @@ module.exports = React.createClass({ onScroll={this.onMessageListScroll} onReadMarkerUpdated={this._updateTopUnreadMessagesBar} showUrlPreview = {this.state.showUrlPreview} - opacity={this.props.opacity} className="mx_RoomView_messagePanel" />); @@ -1797,7 +1795,7 @@ module.exports = React.createClass({ if (this.state.showTopUnreadMessagesBar) { const TopUnreadMessagesBar = sdk.getComponent('rooms.TopUnreadMessagesBar'); topUnreadMessagesBar = ( -
    +
    ); } - let statusBarAreaClass = "mx_RoomView_statusArea mx_fadable"; - if (isStatusAreaExpanded) { - statusBarAreaClass += " mx_RoomView_statusArea_expanded"; - } + const statusBarAreaClass = classNames( + "mx_RoomView_statusArea", + { + "mx_RoomView_statusArea_expanded": isStatusAreaExpanded, + }, + ); + + const fadableSectionClasses = classNames( + "mx_RoomView_body", "mx_fadable", + { + "mx_fadable_faded": this.props.disabled, + }, + ); return (
    @@ -1827,16 +1834,18 @@ module.exports = React.createClass({ onLeaveClick={(myMember && myMember.membership === "join") ? this.onLeaveClick : null} /> { auxPanel } - { topUnreadMessagesBar } - { messagePanel } - { searchResultsPanel } -
    -
    -
    - { statusBar } +
    + { topUnreadMessagesBar } + { messagePanel } + { searchResultsPanel } +
    +
    +
    + { statusBar } +
    + { messageComposer }
    - { messageComposer }
    ); }, diff --git a/src/components/structures/TimelinePanel.js b/src/components/structures/TimelinePanel.js index e3b3b66f97..2bf8a08b98 100644 --- a/src/components/structures/TimelinePanel.js +++ b/src/components/structures/TimelinePanel.js @@ -89,9 +89,6 @@ var TimelinePanel = React.createClass({ // callback which is called when the read-up-to mark is updated. onReadMarkerUpdated: React.PropTypes.func, - // opacity for dynamic UI fading effects - opacity: React.PropTypes.number, - // maximum number of events to show in a timeline timelineCap: React.PropTypes.number, @@ -1157,7 +1154,6 @@ var TimelinePanel = React.createClass({ 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} diff --git a/src/components/structures/UserSettings.js b/src/components/structures/UserSettings.js index b69bea9282..68ea932f93 100644 --- a/src/components/structures/UserSettings.js +++ b/src/components/structures/UserSettings.js @@ -118,6 +118,10 @@ const SETTINGS_LABELS = [ id: 'TextualBody.disableBigEmoji', label: _td('Disable big emoji in chat'), }, + { + id: 'VideoView.flipVideoHorizontally', + label: _td('Mirror local video feed'), + }, /* { id: 'useFixedWidthFont', @@ -271,9 +275,9 @@ module.exports = React.createClass({ MatrixClientPeg.get().on("RoomMember.membership", this._onInviteStateChange); dis.dispatch({ - action: 'ui_opacity', - sideOpacity: 0.3, - middleOpacity: 0.3, + action: 'panel_disable', + sideDisabled: true, + middleDisabled: true, }); this._refreshFromServer(); @@ -311,9 +315,9 @@ module.exports = React.createClass({ componentWillUnmount: function() { this._unmounted = true; dis.dispatch({ - action: 'ui_opacity', - sideOpacity: 1.0, - middleOpacity: 1.0, + action: 'panel_disable', + sideDisabled: false, + middleDisabled: false, }); dis.unregister(this.dispatcherRef); const cli = MatrixClientPeg.get(); @@ -1328,8 +1332,11 @@ module.exports = React.createClass({
    - {_t("Remove + {_t("Remove
    ev.getContent().aliases).reduce((a, b) => { + return a.concat(b); + }, []); const topic = topicEvent ? topicEvent.getContent().topic : ''; const nameMatch = (name || '').toLowerCase().includes(lowerCaseQuery); - const aliasMatch = (canonicalAlias || '').toLowerCase().includes(lowerCaseQuery); + const aliasMatch = aliases.some((alias) => + (alias || '').toLowerCase().includes(lowerCaseQuery), + ); const topicMatch = (topic || '').toLowerCase().includes(lowerCaseQuery); if (!(nameMatch || topicMatch || aliasMatch)) { return; } const avatarEvent = room.currentState.getStateEvents('m.room.avatar', ''); const avatarUrl = avatarEvent ? avatarEvent.getContent().url : undefined; + results.push({ room_id: room.roomId, avatar_url: avatarUrl, - name: name || canonicalAlias, + name: name || canonicalAlias || aliases[0] || _t('Unnamed Room'), }); }); this._processResults(results, query); diff --git a/src/components/views/elements/AppTile.js b/src/components/views/elements/AppTile.js index 3e343e098c..0070af1fb2 100644 --- a/src/components/views/elements/AppTile.js +++ b/src/components/views/elements/AppTile.js @@ -172,18 +172,29 @@ export default React.createClass({ */ _onDeleteClick: function() { if (this._canUserModify()) { - console.log("Delete widget %s", this.props.id); - this.setState({deleting: true}); - MatrixClientPeg.get().sendStateEvent( - this.props.room.roomId, - 'im.vector.modular.widgets', - {}, // empty content - this.props.id, - ).then(() => { - console.log('Deleted widget'); - }, (e) => { - console.error('Failed to delete widget', e); - this.setState({deleting: false}); + // Show delete confirmation dialog + const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); + Modal.createTrackedDialog('Delete Widget', '', QuestionDialog, { + title: _t("Delete Widget"), + description: _t( + "Deleting a widget removes it for all users in this room." + + " Are you sure you want to delete this widget?"), + button: _t("Delete widget"), + onFinished: (confirmed) => { + if (!confirmed) { + return; + } + this.setState({deleting: true}); + MatrixClientPeg.get().sendStateEvent( + this.props.room.roomId, + 'im.vector.modular.widgets', + {}, // empty content + this.props.id, + ).catch((e) => { + console.error('Failed to delete widget', e); + this.setState({deleting: false}); + }); + }, }); } else { console.log("Revoke widget permissions - %s", this.props.id); @@ -305,7 +316,7 @@ export default React.createClass({ let deleteIcon = 'img/cancel.svg'; let deleteClasses = 'mx_filterFlipColor mx_AppTileMenuBarWidget'; if(this._canUserModify()) { - deleteIcon = 'img/cancel-red.svg'; + deleteIcon = 'img/icon-delete-pink.svg'; deleteClasses += ' mx_AppTileMenuBarWidgetDelete'; } diff --git a/src/components/views/elements/EditableItemList.js b/src/components/views/elements/EditableItemList.js index 35e207daef..05ae625515 100644 --- a/src/components/views/elements/EditableItemList.js +++ b/src/components/views/elements/EditableItemList.js @@ -84,7 +84,9 @@ module.exports = React.createClass({ onNewItemChanged: PropTypes.func, onItemAdded: PropTypes.func, onItemEdited: PropTypes.func, - onItemRemoved: PropTypes. func, + onItemRemoved: PropTypes.func, + + canEdit: PropTypes.bool, }, getDefaultProps: function() { @@ -136,14 +138,16 @@ module.exports = React.createClass({ { label }
    { editableItems } - + { this.props.canEdit ? + :
    + }
    ); }, }); diff --git a/src/components/views/groups/GroupMemberInfo.js b/src/components/views/groups/GroupMemberInfo.js index 6f99ee898d..ff100c418a 100644 --- a/src/components/views/groups/GroupMemberInfo.js +++ b/src/components/views/groups/GroupMemberInfo.js @@ -173,7 +173,7 @@ module.exports = withMatrixClient(React.createClass({
    - +
    { avatar } diff --git a/src/components/views/groups/GroupRoomTile.js b/src/components/views/groups/GroupRoomTile.js index 23e53996e3..94dc8e593f 100644 --- a/src/components/views/groups/GroupRoomTile.js +++ b/src/components/views/groups/GroupRoomTile.js @@ -120,8 +120,11 @@ const GroupRoomTile = React.createClass({
    { this.state.name }
    - - + + ); diff --git a/src/components/views/messages/UnknownBody.js b/src/components/views/messages/UnknownBody.js index 083d7ac12e..9a172baf7c 100644 --- a/src/components/views/messages/UnknownBody.js +++ b/src/components/views/messages/UnknownBody.js @@ -25,7 +25,10 @@ module.exports = React.createClass({ render: function() { let tooltip = _t("Removed or unknown message type"); if (this.props.mxEvent.isRedacted()) { - tooltip = _t("Message removed by %(userId)s", {userId: this.props.mxEvent.getSender()}); + const redactedBecauseUserId = this.props.mxEvent.getUnsigned().redacted_because.sender; + tooltip = redactedBecauseUserId ? + _t("Message removed by %(userId)s", { userId: redactedBecauseUserId }) : + _t("Message removed"); } const text = this.props.mxEvent.getContent().body; diff --git a/src/components/views/room_settings/AliasSettings.js b/src/components/views/room_settings/AliasSettings.js index c64e876dbe..cb897c9daf 100644 --- a/src/components/views/room_settings/AliasSettings.js +++ b/src/components/views/room_settings/AliasSettings.js @@ -262,6 +262,7 @@ module.exports = React.createClass({ items={this.state.domainToAliases[localDomain] || []} newItem={this.state.newAlias} onNewItemChanged={this.onNewAliasChanged} + canEdit={this.props.canSetAliases} onItemAdded={this.onLocalAliasAdded} onItemEdited={this.onLocalAliasChanged} onItemRemoved={this.onLocalAliasDeleted} diff --git a/src/components/views/room_settings/RelatedGroupSettings.js b/src/components/views/room_settings/RelatedGroupSettings.js index 7227a951d7..0a2dc3341c 100644 --- a/src/components/views/room_settings/RelatedGroupSettings.js +++ b/src/components/views/room_settings/RelatedGroupSettings.js @@ -27,7 +27,7 @@ module.exports = React.createClass({ propTypes: { roomId: React.PropTypes.string.isRequired, - canSetRelatedRooms: React.PropTypes.bool.isRequired, + canSetRelatedGroups: React.PropTypes.bool.isRequired, relatedGroupsEvent: React.PropTypes.instanceOf(MatrixEvent), }, @@ -37,7 +37,7 @@ module.exports = React.createClass({ getDefaultProps: function() { return { - canSetRelatedRooms: false, + canSetRelatedGroups: false, }; }, @@ -110,6 +110,7 @@ module.exports = React.createClass({ items={this.state.newGroupsList} className={"mx_RelatedGroupSettings"} newItem={this.state.newGroupId} + canEdit={this.props.canSetRelatedGroups} onNewItemChanged={this.onNewGroupChanged} onItemAdded={this.onGroupAdded} onItemEdited={this.onGroupEdited} diff --git a/src/components/views/rooms/ForwardMessage.js b/src/components/views/rooms/ForwardMessage.js index 67e55101e8..b0fba12865 100644 --- a/src/components/views/rooms/ForwardMessage.js +++ b/src/components/views/rooms/ForwardMessage.js @@ -30,10 +30,9 @@ module.exports = React.createClass({ componentWillMount: function() { dis.dispatch({ - action: 'ui_opacity', - leftOpacity: 1.0, - rightOpacity: 0.3, - middleOpacity: 0.5, + action: 'panel_disable', + rightDisabled: true, + middleDisabled: true, }); }, @@ -43,9 +42,9 @@ module.exports = React.createClass({ componentWillUnmount: function() { dis.dispatch({ - action: 'ui_opacity', - sideOpacity: 1.0, - middleOpacity: 1.0, + action: 'panel_disable', + sideDisabled: false, + middleDisabled: false, }); document.removeEventListener('keydown', this._onKeyDown); }, diff --git a/src/components/views/rooms/LinkPreviewWidget.js b/src/components/views/rooms/LinkPreviewWidget.js index 10c809e0f1..d69a8fbcf1 100644 --- a/src/components/views/rooms/LinkPreviewWidget.js +++ b/src/components/views/rooms/LinkPreviewWidget.js @@ -133,8 +133,9 @@ module.exports = React.createClass({ { p["og:description"] }
    - +
    ); }, diff --git a/src/components/views/rooms/MemberInfo.js b/src/components/views/rooms/MemberInfo.js index e281a93558..3abff39652 100644 --- a/src/components/views/rooms/MemberInfo.js +++ b/src/components/views/rooms/MemberInfo.js @@ -39,6 +39,7 @@ import { findReadReceiptFromUserId } from '../../../utils/Receipt'; import withMatrixClient from '../../../wrappers/withMatrixClient'; import AccessibleButton from '../elements/AccessibleButton'; import GeminiScrollbar from 'react-gemini-scrollbar'; +import RoomViewStore from '../../../stores/RoomViewStore'; module.exports = withMatrixClient(React.createClass({ @@ -81,6 +82,7 @@ module.exports = withMatrixClient(React.createClass({ cli.on("Room.receipt", this.onRoomReceipt); cli.on("RoomState.events", this.onRoomStateEvents); cli.on("RoomMember.name", this.onRoomMemberName); + cli.on("RoomMember.membership", this.onRoomMemberMembership); cli.on("accountData", this.onAccountData); this._checkIgnoreState(); @@ -91,7 +93,7 @@ module.exports = withMatrixClient(React.createClass({ }, componentWillReceiveProps: function(newProps) { - if (this.props.member.userId != newProps.member.userId) { + if (this.props.member.userId !== newProps.member.userId) { this._updateStateForNewMember(newProps.member); } }, @@ -107,6 +109,7 @@ module.exports = withMatrixClient(React.createClass({ client.removeListener("Room.receipt", this.onRoomReceipt); client.removeListener("RoomState.events", this.onRoomStateEvents); client.removeListener("RoomMember.name", this.onRoomMemberName); + client.removeListener("RoomMember.membership", this.onRoomMemberMembership); client.removeListener("accountData", this.onAccountData); } if (this._cancelDeviceList) { @@ -122,12 +125,12 @@ module.exports = withMatrixClient(React.createClass({ _disambiguateDevices: function(devices) { const names = Object.create(null); for (let i = 0; i < devices.length; i++) { - var name = devices[i].getDisplayName(); + const name = devices[i].getDisplayName(); const indexList = names[name] || []; indexList.push(i); names[name] = indexList; } - for (name in names) { + for (const name in names) { if (names[name].length > 1) { names[name].forEach((j)=>{ devices[j].ambiguous = true; @@ -141,7 +144,7 @@ module.exports = withMatrixClient(React.createClass({ return; } - if (userId == this.props.member.userId) { + if (userId === this.props.member.userId) { // no need to re-download the whole thing; just update our copy of // the list. @@ -186,8 +189,12 @@ module.exports = withMatrixClient(React.createClass({ this.forceUpdate(); }, + onRoomMemberMembership: function(ev, member) { + if (this.props.member.userId === member.userId) this.forceUpdate(); + }, + onAccountData: function(ev) { - if (ev.getType() == 'm.direct') { + if (ev.getType() === 'm.direct') { this.forceUpdate(); } }, @@ -242,7 +249,9 @@ module.exports = withMatrixClient(React.createClass({ ignoredUsers.push(this.props.member.userId); } - this.props.matrixClient.setIgnoredUsers(ignoredUsers).then(() => this.setState({isIgnoring: !this.state.isIgnoring})); + this.props.matrixClient.setIgnoredUsers(ignoredUsers).then(() => { + return this.setState({isIgnoring: !this.state.isIgnoring}); + }); }, onKick: function() { @@ -252,7 +261,7 @@ module.exports = withMatrixClient(React.createClass({ Modal.createTrackedDialog('Confirm User Action Dialog', 'onKick', ConfirmUserActionDialog, { member: this.props.member, action: kickLabel, - askReason: membership == "join", + askReason: membership === "join", danger: true, onFinished: (proceed, reason) => { if (!proceed) return; @@ -284,15 +293,15 @@ module.exports = withMatrixClient(React.createClass({ const ConfirmUserActionDialog = sdk.getComponent("dialogs.ConfirmUserActionDialog"); Modal.createTrackedDialog('Confirm User Action Dialog', 'onBanOrUnban', ConfirmUserActionDialog, { member: this.props.member, - action: this.props.member.membership == 'ban' ? _t("Unban") : _t("Ban"), - askReason: this.props.member.membership != 'ban', - danger: this.props.member.membership != 'ban', + action: this.props.member.membership === 'ban' ? _t("Unban") : _t("Ban"), + askReason: this.props.member.membership !== 'ban', + danger: this.props.member.membership !== 'ban', onFinished: (proceed, reason) => { if (!proceed) return; this.setState({ updating: this.state.updating + 1 }); let promise; - if (this.props.member.membership == 'ban') { + if (this.props.member.membership === 'ban') { promise = this.props.matrixClient.unban( this.props.member.roomId, this.props.member.userId, ); @@ -327,15 +336,11 @@ module.exports = withMatrixClient(React.createClass({ const roomId = this.props.member.roomId; const target = this.props.member.userId; const room = this.props.matrixClient.getRoom(roomId); - if (!room) { - return; - } - const powerLevelEvent = room.currentState.getStateEvents( - "m.room.power_levels", "", - ); - if (!powerLevelEvent) { - return; - } + if (!room) return; + + const powerLevelEvent = room.currentState.getStateEvents("m.room.power_levels", ""); + if (!powerLevelEvent) return; + const isMuted = this.state.muted; const powerLevels = powerLevelEvent.getContent(); const levelToSend = ( @@ -350,7 +355,7 @@ module.exports = withMatrixClient(React.createClass({ } level = parseInt(level); - if (level !== NaN) { + if (!isNaN(level)) { this.setState({ updating: this.state.updating + 1 }); this.props.matrixClient.setPowerLevel(roomId, target, level, powerLevelEvent).then( function() { @@ -375,19 +380,14 @@ module.exports = withMatrixClient(React.createClass({ const roomId = this.props.member.roomId; const target = this.props.member.userId; const room = this.props.matrixClient.getRoom(roomId); - if (!room) { - return; - } - const powerLevelEvent = room.currentState.getStateEvents( - "m.room.power_levels", "", - ); - if (!powerLevelEvent) { - return; - } + if (!room) return; + + const powerLevelEvent = room.currentState.getStateEvents("m.room.power_levels", ""); + if (!powerLevelEvent) return; + const me = room.getMember(this.props.matrixClient.credentials.userId); - if (!me) { - return; - } + if (!me) return; + const defaultLevel = powerLevelEvent.getContent().users_default; let modLevel = me.powerLevel - 1; if (modLevel > 50 && defaultLevel < 50) modLevel = 50; // try to stick with the vector level defaults @@ -400,7 +400,7 @@ module.exports = withMatrixClient(React.createClass({ // get out of sync if we force setState here! console.log("Mod toggle success"); }, function(err) { - if (err.errcode == 'M_GUEST_ACCESS_FORBIDDEN') { + if (err.errcode === 'M_GUEST_ACCESS_FORBIDDEN') { dis.dispatch({action: 'view_set_mxid'}); } else { console.error("Toggle moderator error:" + err); @@ -436,7 +436,6 @@ module.exports = withMatrixClient(React.createClass({ }, onPowerChange: function(powerLevel) { - const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); const roomId = this.props.member.roomId; const target = this.props.member.userId; const room = this.props.matrixClient.getRoom(roomId); @@ -497,19 +496,14 @@ module.exports = withMatrixClient(React.createClass({ modifyLevel: false, }; const room = this.props.matrixClient.getRoom(member.roomId); - if (!room) { - return defaultPerms; - } - const powerLevels = room.currentState.getStateEvents( - "m.room.power_levels", "", - ); - if (!powerLevels) { - return defaultPerms; - } + if (!room) return defaultPerms; + + const powerLevels = room.currentState.getStateEvents("m.room.power_levels", ""); + if (!powerLevels) return defaultPerms; + const me = room.getMember(this.props.matrixClient.credentials.userId); - if (!me) { - return defaultPerms; - } + if (!me) return defaultPerms; + const them = member; return { can: this._calculateCanPermissions( @@ -545,14 +539,13 @@ module.exports = withMatrixClient(React.createClass({ can.ban = me.powerLevel >= powerLevels.ban; can.mute = me.powerLevel >= editPowerLevel; can.toggleMod = me.powerLevel > them.powerLevel && them.powerLevel >= levelToSend; - can.modifyLevel = me.powerLevel > them.powerLevel; + can.modifyLevel = me.powerLevel > them.powerLevel && me.powerLevel >= editPowerLevel; return can; }, _isMuted: function(member, powerLevelContent) { - if (!powerLevelContent || !member) { - return false; - } + if (!powerLevelContent || !member) return false; + const levelToSend = ( (powerLevelContent.events ? powerLevelContent.events["m.room.message"] : null) || powerLevelContent.events_default @@ -568,14 +561,15 @@ module.exports = withMatrixClient(React.createClass({ }, onMemberAvatarClick: function() { - const avatarUrl = this.props.member.user ? this.props.member.user.avatarUrl : this.props.member.events.member.getContent().avatar_url; + const member = this.props.member; + const avatarUrl = member.user ? member.user.avatarUrl : member.events.member.getContent().avatar_url; if(!avatarUrl) return; const httpUrl = this.props.matrixClient.mxcUrlToHttp(avatarUrl); const ImageView = sdk.getComponent("elements.ImageView"); const params = { src: httpUrl, - name: this.props.member.name, + name: member.name, }; Modal.createDialog(ImageView, params, "mx_Dialog_lightbox"); @@ -589,9 +583,7 @@ module.exports = withMatrixClient(React.createClass({ }, _renderDevices: function() { - if (!this._enableDevices) { - return null; - } + if (!this._enableDevices) return null; const devices = this.state.devices; const MemberDeviceInfo = sdk.getComponent('rooms.MemberDeviceInfo'); @@ -630,6 +622,7 @@ module.exports = withMatrixClient(React.createClass({ let ignoreButton = null; let insertPillButton = null; + let inviteUserButton = null; let readReceiptButton = null; // Only allow the user to ignore the user if its not ourselves @@ -673,9 +666,30 @@ module.exports = withMatrixClient(React.createClass({ ); } + + if (!member || !member.membership || member.membership === 'leave') { + const roomId = member && member.roomId ? member.roomId : RoomViewStore.getRoomId(); + const onInviteUserButton = async () => { + try { + await cli.invite(roomId, member.userId); + } catch (err) { + const ErrorDialog = sdk.getComponent('dialogs.ErrorDialog'); + Modal.createTrackedDialog('Failed to invite', '', ErrorDialog, { + title: _t('Failed to invite'), + description: ((err && err.message) ? err.message : "Operation failed"), + }); + } + }; + + inviteUserButton = ( + + { _t('Invite') } + + ); + } } - if (!ignoreButton && !readReceiptButton && !insertPillButton) return null; + if (!ignoreButton && !readReceiptButton && !insertPillButton && !inviteUserButton) return null; return (
    @@ -684,13 +698,20 @@ module.exports = withMatrixClient(React.createClass({ { readReceiptButton } { insertPillButton } { ignoreButton } + { inviteUserButton }
    ); }, render: function() { - let startChat, kickButton, banButton, muteButton, giveModButton, spinner; + let startChat; + let kickButton; + let banButton; + let muteButton; + let giveModButton; + let spinner; + if (this.props.member.userId !== this.props.matrixClient.credentials.userId) { const dmRoomMap = new DMRoomMap(this.props.matrixClient); const dmRooms = dmRoomMap.getDMRoomsForUserId(this.props.member.userId); @@ -704,7 +725,7 @@ module.exports = withMatrixClient(React.createClass({ const me = room.getMember(this.props.matrixClient.credentials.userId); const highlight = ( room.getUnreadNotificationCount('highlight') > 0 || - me.membership == "invite" + me.membership === "invite" ); tiles.push( , ); @@ -757,7 +778,7 @@ module.exports = withMatrixClient(React.createClass({ } if (this.state.can.ban) { let label = _t("Ban"); - if (this.props.member.membership == 'ban') { + if (this.props.member.membership === 'ban') { label = _t("Unban"); } banButton = ( @@ -783,9 +804,6 @@ module.exports = withMatrixClient(React.createClass({ ; } - // TODO: we should have an invite button if this MemberInfo is showing a user who isn't actually in the current room yet - // e.g. clicking on a linkified userid in a room - let adminTools; if (kickButton || banButton || muteButton || giveModButton) { adminTools = @@ -803,16 +821,39 @@ module.exports = withMatrixClient(React.createClass({ const memberName = this.props.member.name; + let presenceState; + let presenceLastActiveAgo; + let presenceCurrentlyActive; + if (this.props.member.user) { - var presenceState = this.props.member.user.presence; - var presenceLastActiveAgo = this.props.member.user.lastActiveAgo; - const presenceLastTs = this.props.member.user.lastPresenceTs; - var presenceCurrentlyActive = this.props.member.user.currentlyActive; + presenceState = this.props.member.user.presence; + presenceLastActiveAgo = this.props.member.user.lastActiveAgo; + presenceCurrentlyActive = this.props.member.user.currentlyActive; + } + + let roomMemberDetails = null; + + if (this.props.member.roomId) { // is in room + const PowerSelector = sdk.getComponent('elements.PowerSelector'); + const PresenceLabel = sdk.getComponent('rooms.PresenceLabel'); + roomMemberDetails =
    +
    + { _t("Level:") } + + +
    +
    + +
    +
    ; } const MemberAvatar = sdk.getComponent('avatars.MemberAvatar'); - const PowerSelector = sdk.getComponent('elements.PowerSelector'); - const PresenceLabel = sdk.getComponent('rooms.PresenceLabel'); const EmojiText = sdk.getComponent('elements.EmojiText'); return (
    @@ -828,14 +869,7 @@ module.exports = withMatrixClient(React.createClass({
    { this.props.member.userId }
    -
    - { _t("Level:") } -
    -
    - -
    + { roomMemberDetails }
    { this._renderUserOptions() } diff --git a/src/components/views/rooms/MessageComposer.js b/src/components/views/rooms/MessageComposer.js index 8e27520d89..93f20b8ec3 100644 --- a/src/components/views/rooms/MessageComposer.js +++ b/src/components/views/rooms/MessageComposer.js @@ -371,7 +371,7 @@ export default class MessageComposer extends React.Component { ); return ( -
    +
    { controls } @@ -410,9 +410,6 @@ MessageComposer.propTypes = { // callback when a file to upload is chosen uploadFile: React.PropTypes.func.isRequired, - // opacity for dynamic UI fading effects - opacity: React.PropTypes.number, - // string representing the current room app drawer state showApps: React.PropTypes.bool, }; diff --git a/src/components/views/rooms/PinnedEventsPanel.js b/src/components/views/rooms/PinnedEventsPanel.js index deea03f030..5b1b8a4590 100644 --- a/src/components/views/rooms/PinnedEventsPanel.js +++ b/src/components/views/rooms/PinnedEventsPanel.js @@ -95,7 +95,9 @@ module.exports = React.createClass({ return (
    - + + +

    { _t("Pinned Messages") }

    { tiles }
    diff --git a/src/components/views/rooms/RoomHeader.js b/src/components/views/rooms/RoomHeader.js index 4df0ff738c..4dfbdb3644 100644 --- a/src/components/views/rooms/RoomHeader.js +++ b/src/components/views/rooms/RoomHeader.js @@ -281,8 +281,11 @@ module.exports = React.createClass({
    - {_t("Remove + {_t("Remove
    ); diff --git a/src/components/views/rooms/RoomSettings.js b/src/components/views/rooms/RoomSettings.js index 9934456597..dbdcdf596a 100644 --- a/src/components/views/rooms/RoomSettings.js +++ b/src/components/views/rooms/RoomSettings.js @@ -19,7 +19,6 @@ import Promise from 'bluebird'; import React from 'react'; import { _t, _tJsx, _td } from '../../../languageHandler'; import MatrixClientPeg from '../../../MatrixClientPeg'; -import SdkConfig from '../../../SdkConfig'; import sdk from '../../../index'; import Modal from '../../../Modal'; import ObjectUtils from '../../../ObjectUtils'; @@ -158,9 +157,9 @@ module.exports = React.createClass({ }); dis.dispatch({ - action: 'ui_opacity', - sideOpacity: 0.3, - middleOpacity: 0.3, + action: 'panel_disable', + sideDisabled: true, + middleDisabled: true, }); }, @@ -171,9 +170,9 @@ module.exports = React.createClass({ } dis.dispatch({ - action: 'ui_opacity', - sideOpacity: 1.0, - middleOpacity: 1.0, + action: 'panel_disable', + sideDisabled: false, + middleDisabled: false, }); }, diff --git a/src/components/views/voip/VideoView.js b/src/components/views/voip/VideoView.js index 8f062d27ae..748673f1a5 100644 --- a/src/components/views/voip/VideoView.js +++ b/src/components/views/voip/VideoView.js @@ -18,10 +18,13 @@ limitations under the License. import React from 'react'; import ReactDOM from 'react-dom'; +import classNames from 'classnames'; import sdk from '../../../index'; import dis from '../../../dispatcher'; +import UserSettingsStore from '../../../UserSettingsStore'; + module.exports = React.createClass({ displayName: 'VideoView', @@ -108,14 +111,18 @@ module.exports = React.createClass({ document.mozFullScreenElement || document.webkitFullscreenElement); const maxVideoHeight = fullscreenElement ? null : this.props.maxHeight; - + const localVideoFeedClasses = classNames("mx_VideoView_localVideoFeed", + { "mx_VideoView_localVideoFeed_flipped": + UserSettingsStore.getSyncedSetting('VideoView.flipVideoHorizontally', false), + }, + ); return (
    -
    +
    diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index ea5c12d86c..a541e9e130 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -240,6 +240,7 @@ "Unignore": "Unignore", "Ignore": "Ignore", "Jump to read receipt": "Jump to read receipt", + "Invite": "Invite", "User Options": "User Options", "Direct chats": "Direct chats", "Unmute": "Unmute", @@ -451,6 +452,7 @@ "You are about to be taken to a third-party site so you can authenticate your account for use with %(integrationsUrl)s. Do you wish to continue?": "You are about to be taken to a third-party site so you can authenticate your account for use with %(integrationsUrl)s. Do you wish to continue?", "Removed or unknown message type": "Removed or unknown message type", "Message removed by %(userId)s": "Message removed by %(userId)s", + "Message removed": "Message removed", "Robot check is currently unavailable on desktop - please use a web browser": "Robot check is currently unavailable on desktop - please use a web browser", "This Home Server would like to make sure you are not a robot": "This Home Server would like to make sure you are not a robot", "Sign in with CAS": "Sign in with CAS", @@ -494,10 +496,13 @@ "Are you sure you want to remove '%(roomName)s' from %(groupId)s?": "Are you sure you want to remove '%(roomName)s' from %(groupId)s?", "Removing a room from the community will also remove it from the community page.": "Removing a room from the community will also remove it from the community page.", "Remove": "Remove", + "Remove this room from the community": "Remove this room from the community", "Unknown Address": "Unknown Address", "NOTE: Apps are not end-to-end encrypted": "NOTE: Apps are not end-to-end encrypted", "Do you want to load widget from URL:": "Do you want to load widget from URL:", "Allow": "Allow", + "Delete Widget": "Delete Widget", + "Deleting a widget removes it for all users in this room. Are you sure you want to delete this widget?": "Deleting a widget removes it for all users in this room. Are you sure you want to delete this widget?", "Delete widget": "Delete widget", "Revoke widget access": "Revoke widget access", "Edit": "Edit", @@ -677,17 +682,17 @@ "Leave %(groupName)s?": "Leave %(groupName)s?", "Leave": "Leave", "Unable to leave room": "Unable to leave room", + "Community Settings": "Community Settings", "Add rooms to this community": "Add rooms to this community", "Featured Rooms:": "Featured Rooms:", "Featured Users:": "Featured Users:", "%(inviter)s has invited you to join this community": "%(inviter)s has invited you to join this community", - "You are a member of this community": "You are a member of this community", "You are an administrator of this community": "You are an administrator of this community", + "You are a member of this community": "You are a member of this community", "Community Member Settings": "Community Member Settings", "Publish this community on your profile": "Publish this community on your profile", "Long Description (HTML)": "Long Description (HTML)", "Description": "Description", - "Community Settings": "Community Settings", "Community %(groupId)s not found": "Community %(groupId)s not found", "This Home server does not support communities": "This Home server does not support communities", "Failed to load %(groupId)s": "Failed to load %(groupId)s", @@ -753,6 +758,7 @@ "Disable Emoji suggestions while typing": "Disable Emoji suggestions while typing", "Hide avatars in user and room mentions": "Hide avatars in user and room mentions", "Disable big emoji in chat": "Disable big emoji in chat", + "Mirror local video feed": "Mirror local video feed", "Opt out of analytics": "Opt out of analytics", "Disable Peer-to-Peer for 1:1 calls": "Disable Peer-to-Peer for 1:1 calls", "Never send encrypted messages to unverified devices from this device": "Never send encrypted messages to unverified devices from this device",