diff --git a/README.md b/README.md index 690e1041..a15917a4 100644 --- a/README.md +++ b/README.md @@ -22,18 +22,34 @@ into the `vector` directory and run your own server. Development =========== -You can work on any of the source files within Vector with the setup above, -and your changes will cause an instant rebuild. If you also need to make -changes to the react sdk, you can: -1. Link the react sdk package into the example: +For simple tweaks, you can work on any of the source files within Vector with the +setup above, and your changes will cause an instant rebuild. + +However, all serious development on Vector happens on the `develop` branch. This typically +depends on the `develop` snapshot versions of `matrix-react-sdk` and `matrix-js-sdk` +too, which isn't expressed in Vector's `package.json`. To do this, check out +the `develop` branches of these libraries and then use `npm link` to tell Vector +about them: + +1. `git clone git@github.com:matrix-org/matrix-react-sdk.git` +2. `cd matrix-react-sdk` +3. `git checkout develop` +4. `npm install` +5. `npm start` (to start the dev rebuilder) +6. `cd ../vector-web` +7. Link the react sdk package into the example: `npm link path/to/your/react/sdk` -2. Start the development rebuilder in your react SDK directory: - `npm start` + +Similarly, you may need to `npm link path/to/your/js/sdk` in your `matrix-react-sdk` +directory. If you add or remove any components from the Vector skin, you will need to rebuild the skin's index by running, `npm run reskindex`. +You may need to run `npm i source-map-loader` in matrix-js-sdk if you get errors +about "Cannot resolve module 'source-map-loader'" due to shortcomings in webpack. + Deployment ========== diff --git a/package.json b/package.json index 429e6139..02f793a1 100644 --- a/package.json +++ b/package.json @@ -27,8 +27,8 @@ "filesize": "^3.1.2", "flux": "~2.0.3", "linkifyjs": "^2.0.0-beta.4", - "matrix-js-sdk": "^0.2.1", - "matrix-react-sdk": "^0.0.1", + "matrix-js-sdk": "^0.3.0", + "matrix-react-sdk": "^0.0.2", "q": "^1.4.1", "react": "^0.13.3", "react-loader": "^1.4.0" diff --git a/src/Avatar.js b/src/Avatar.js index 2f83ebd9..afc5e9dd 100644 --- a/src/Avatar.js +++ b/src/Avatar.js @@ -20,14 +20,17 @@ var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg'); module.exports = { avatarUrlForMember: function(member, width, height, resizeMethod) { - var url = MatrixClientPeg.get().getAvatarUrlForMember( - member, + var url = member.getAvatarUrl( + MatrixClientPeg.get().getHomeserverUrl(), width, height, resizeMethod ); if (!url) { - url = this.defaultAvatarUrlForString(member.userId); + // member can be null here currently since on invites, the JS SDK + // does not have enough info to build a RoomMember object for + // the inviter. + url = this.defaultAvatarUrlForString(member ? member.userId : ''); } return url; }, diff --git a/src/ContextualMenu.js b/src/ContextualMenu.js index cdfff952..7865e45a 100644 --- a/src/ContextualMenu.js +++ b/src/ContextualMenu.js @@ -49,15 +49,25 @@ module.exports = { var position = { top: props.top - 20, - right: props.right + 8, }; + var chevron = null; + if (props.left) { + chevron = + position.left = props.left + 8; + } else { + chevron = + position.right = props.right + 8; + } + + var className = 'mx_ContextualMenu_wrapper'; + // FIXME: If a menu uses getDefaultProps it clobbers the onFinished // property set here so you can't close the menu from a button click! var menu = ( -
+
- + {chevron}
diff --git a/src/controllers/molecules/voip/CallView.js b/src/controllers/molecules/voip/CallView.js index d511b9d4..ab712148 100644 --- a/src/controllers/molecules/voip/CallView.js +++ b/src/controllers/molecules/voip/CallView.js @@ -74,8 +74,10 @@ module.exports = { ); if (call) { call.setLocalVideoElement(this.getVideoView().getLocalVideoElement()); - // N.B. the remote video element is used for playback for audio for voice calls call.setRemoteVideoElement(this.getVideoView().getRemoteVideoElement()); + // give a separate element for audio stream playback - both for voice calls + // and for the voice stream of screen captures + call.setRemoteAudioElement(this.getVideoView().getRemoteAudioElement()); } if (call && call.type === "video" && call.state !== 'ended') { // if this call is a conf call, don't display local video as the @@ -88,6 +90,7 @@ module.exports = { else { this.getVideoView().getLocalVideoElement().style.display = "none"; this.getVideoView().getRemoteVideoElement().style.display = "none"; + dis.dispatch({action: 'video_fullscreen', fullscreen: false}); } } }; diff --git a/src/controllers/organisms/RoomList.js b/src/controllers/organisms/RoomList.js index 2602315a..151a6ca2 100644 --- a/src/controllers/organisms/RoomList.js +++ b/src/controllers/organisms/RoomList.js @@ -33,6 +33,8 @@ module.exports = { cli.on("Room", this.onRoom); cli.on("Room.timeline", this.onRoomTimeline); cli.on("Room.name", this.onRoomName); + cli.on("RoomState.events", this.onRoomStateEvents); + cli.on("RoomMember.name", this.onRoomMemberName); var rooms = this.getRoomList(); this.setState({ @@ -52,6 +54,11 @@ module.exports = { case 'call_state': this._recheckCallElement(this.props.selectedRoom); break; + case 'view_tooltip': + this.tooltip = payload.tooltip; + this._repositionTooltip(); + if (this.tooltip) this.tooltip.style.display = 'block'; + break } }, @@ -61,6 +68,7 @@ module.exports = { MatrixClientPeg.get().removeListener("Room", this.onRoom); MatrixClientPeg.get().removeListener("Room.timeline", this.onRoomTimeline); MatrixClientPeg.get().removeListener("Room.name", this.onRoomName); + MatrixClientPeg.get().removeListener("RoomState.events", this.onRoomStateEvents); } }, @@ -105,6 +113,15 @@ module.exports = { this.refreshRoomList(); }, + onRoomStateEvents: function(ev, state) { + setTimeout(this.refreshRoomList, 0); + }, + + onRoomMemberName: function(ev, member) { + setTimeout(this.refreshRoomList, 0); + }, + + refreshRoomList: function() { var rooms = this.getRoomList(); this.setState({ @@ -150,6 +167,13 @@ module.exports = { }); }, + _repositionTooltip: function(e) { + if (this.tooltip && this.tooltip.parentElement) { + var scroll = this.getDOMNode(); + this.tooltip.style.top = (scroll.parentElement.offsetTop + this.tooltip.parentElement.offsetTop - scroll.scrollTop) + "px"; + } + }, + makeRoomTiles: function() { var self = this; var RoomTile = sdk.getComponent("molecules.RoomTile"); @@ -159,6 +183,7 @@ module.exports = { = 0 && count < this.state.messageCap; --i) { + var mxEv = this.state.room.timeline[i]; + + if (!EventTile.supportsEventType(mxEv.getType())) { + continue; + } + + var continuation = false; + var last = false; + var dateSeparator = null; + if (i == this.state.room.timeline.length - 1) { + last = true; + } + if (i > 0 && count < this.state.messageCap - 1) { + if (this.state.room.timeline[i].sender && + this.state.room.timeline[i - 1].sender && + (this.state.room.timeline[i].sender.userId === + this.state.room.timeline[i - 1].sender.userId) && + (this.state.room.timeline[i].getType() == + this.state.room.timeline[i - 1].getType()) + ) + { + continuation = true; + } + + var ts0 = this.state.room.timeline[i - 1].getTs(); + var ts1 = this.state.room.timeline[i].getTs(); + if (new Date(ts0).toDateString() !== new Date(ts1).toDateString()) { + dateSeparator = ; + continuation = false; + } + } + + if (i === 1) { // n.b. 1, not 0, as the 0th event is an m.room.create and so doesn't show on the timeline + var ts1 = this.state.room.timeline[i].getTs(); + dateSeparator =
  • ; + continuation = false; + } + + ret.unshift( +
  • + ); + if (dateSeparator) { + ret.unshift(dateSeparator); + } + ++count; + } + return ret; + }, + + uploadNewState: function(new_name, new_topic, new_join_rule, new_history_visibility, new_power_levels) { + var old_name = this.state.room.name; + + var old_topic = this.state.room.currentState.getStateEvents('m.room.topic', ''); + if (old_topic) { + old_topic = old_topic.getContent().topic; + } else { + old_topic = ""; + } + + var old_join_rule = this.state.room.currentState.getStateEvents('m.room.join_rules', ''); + if (old_join_rule) { + old_join_rule = old_join_rule.getContent().join_rule; + } else { + old_join_rule = "invite"; + } + + var old_history_visibility = this.state.room.currentState.getStateEvents('m.room.history_visibility', ''); + if (old_history_visibility) { + old_history_visibility = old_history_visibility.getContent().history_visibility; + } else { + old_history_visibility = "shared"; + } + + var deferreds = []; + + if (old_name != new_name && new_name != undefined && new_name) { + deferreds.push( + MatrixClientPeg.get().setRoomName(this.state.room.roomId, new_name) + ); + } + + if (old_topic != new_topic && new_topic != undefined) { + deferreds.push( + MatrixClientPeg.get().setRoomTopic(this.state.room.roomId, new_topic) + ); + } + + if (old_join_rule != new_join_rule && new_join_rule != undefined) { + deferreds.push( + MatrixClientPeg.get().sendStateEvent( + this.state.room.roomId, "m.room.join_rules", { + join_rule: new_join_rule, + }, "" + ) + ); + } + + if (old_history_visibility != new_history_visibility && new_history_visibility != undefined) { + deferreds.push( + MatrixClientPeg.get().sendStateEvent( + this.state.room.roomId, "m.room.history_visibility", { + history_visibility: new_history_visibility, + }, "" + ) + ); + } + + if (new_power_levels) { + deferreds.push( + MatrixClientPeg.get().sendStateEvent( + this.state.room.roomId, "m.room.power_levels", new_power_levels, "" + ) + ); + } + + if (deferreds.length) { + var self = this; + q.all(deferreds).fail(function(err) { + var ErrorDialog = sdk.getComponent("organisms.ErrorDialog"); + Modal.createDialog(ErrorDialog, { + title: "Failed to set state", + description: err.toString() + }); + }).finally(function() { + self.setState({ + uploadingRoomSettings: false, + }); + }); + } else { + this.setState({ + editingRoomSettings: false, + uploadingRoomSettings: false, + }); + } + } +}; diff --git a/src/controllers/templates/Register.js b/src/controllers/templates/Register.js new file mode 100644 index 00000000..5f88d590 --- /dev/null +++ b/src/controllers/templates/Register.js @@ -0,0 +1,58 @@ +/* +Copyright 2015 OpenMarket Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +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. +*/ + +'use strict'; + +var extend = require('matrix-react-sdk/lib/extend'); +var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg'); +var BaseRegisterController = require('matrix-react-sdk/lib/controllers/templates/Register.js'); + +var RegisterController = {}; +extend(RegisterController, BaseRegisterController); + +RegisterController.onRegistered = function(user_id, access_token) { + MatrixClientPeg.replaceUsingAccessToken( + this.state.hs_url, this.state.is_url, user_id, access_token + ); + + this.setState({ + step: 'profile', + busy: true + }); + + var self = this; + var cli = MatrixClientPeg.get(); + cli.getProfileInfo(cli.credentials.userId).done(function(result) { + self.setState({ + avatarUrl: result.avatar_url, + busy: false + }); + }, + function(err) { + console.err(err); + self.setState({ + busy: false + }); + }); +}; + +RegisterController.onAccountReady = function() { + if (this.props.onLoggedIn) { + this.props.onLoggedIn(); + } +}; + +module.exports = RegisterController; diff --git a/src/skins/vector/css/common.css b/src/skins/vector/css/common.css index 8afba88e..93012c0f 100644 --- a/src/skins/vector/css/common.css +++ b/src/skins/vector/css/common.css @@ -14,6 +14,13 @@ See the License for the specific language governing permissions and limitations under the License. */ +html { + /* hack to stop overscroll bounce on OSX and iOS. + N.B. Breaks things when we have legitimate horizontal overscroll */ + height: 100%; + overflow: hidden; +} + body { font-family: 'Lato', Helvetica, Arial, Sans-Serif; font-size: 16px; @@ -34,6 +41,12 @@ h2 { margin-bottom: 16px; } +a:hover, +a:link, +a:visited { + color: #80CEF4; +} + .mx_ContextualMenu_background { position: fixed; top: 0; @@ -54,18 +67,29 @@ h2 { padding: 6px; } -.mx_ContextualMenu_chevron { +.mx_ContextualMenu_chevron_right { padding: 12px; position: absolute; right: -21px; top: 0px; } +.mx_ContextualMenu_chevron_left { + padding: 12px; + position: absolute; + left: -21px; + top: 0px; +} + .mx_ContextualMenu_field { padding: 3px 6px 3px 6px; cursor: pointer; } +.mx_ContextualMenu_spinner { + display: block; + margin: 0 auto; +} .mx_Dialog_background { position: fixed; diff --git a/src/skins/vector/css/hide.css b/src/skins/vector/css/hide.css index 381f9a18..fbc2db20 100644 --- a/src/skins/vector/css/hide.css +++ b/src/skins/vector/css/hide.css @@ -1,7 +1,6 @@ .mx_RoomDropTarget, .mx_RoomList_favourites_label, .mx_RoomList_archive_label, -.mx_LeftPanel_hideButton, .mx_RoomHeader_search, .mx_RoomSettings_encrypt, .mx_CreateRoom_encrypt, diff --git a/src/skins/vector/css/molecules/MessageTile.css b/src/skins/vector/css/molecules/EventTile.css similarity index 61% rename from src/skins/vector/css/molecules/MessageTile.css rename to src/skins/vector/css/molecules/EventTile.css index 50a12c3a..1cd2fa46 100644 --- a/src/skins/vector/css/molecules/MessageTile.css +++ b/src/skins/vector/css/molecules/EventTile.css @@ -14,14 +14,14 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_MessageTile { +.mx_EventTile { max-width: 100%; clear: both; margin-top: 32px; margin-left: 56px; } -.mx_MessageTile_avatar { +.mx_EventTile_avatar { padding-left: 12px; padding-right: 12px; margin-left: -64px; @@ -29,17 +29,17 @@ limitations under the License. float: left; } -.mx_MessageTile_avatar img { +.mx_EventTile_avatar img { background-color: #dbdbdb; border-radius: 20px; border: 0px; } -.mx_MessageTile_continuation { +.mx_EventTile_continuation { margin-top: 8px ! important; } -.mx_MessageTile .mx_SenderProfile { +.mx_EventTile .mx_SenderProfile { color: #454545; opacity: 0.5; font-size: 14px; @@ -47,35 +47,35 @@ limitations under the License. display: block; } -.mx_MessageTile .mx_MessageTimestamp { +.mx_EventTile .mx_MessageTimestamp { color: #454545; opacity: 0.5; font-size: 14px; float: right; } -.mx_MessageTile_content { +.mx_EventTile_content { padding-right: 100px; display: block; } -.mx_MessageTile_notice .mx_MessageTile_content { +.mx_EventTile_notice .mx_MessageTile_content { opacity: 0.5; } -.mx_MessageTile_sending { +.mx_EventTile_sending { color: #ddd; } -.mx_MessageTile_notSent { +.mx_EventTile_notSent { color: #f11; } -.mx_MessageTile_highlight { - color: #00f; +.mx_EventTile_highlight { + color: #FF0064; } -.mx_MessageTile_msgOption { +.mx_EventTile_msgOption { float: right; } @@ -83,10 +83,30 @@ limitations under the License. display: none; } -.mx_MessageTile_last .mx_MessageTimestamp { +.mx_EventTile_last .mx_MessageTimestamp { display: block; } -.mx_MessageTile:hover .mx_MessageTimestamp { +.mx_EventTile:hover .mx_MessageTimestamp { display: block; } + +.mx_EventTile_editButton { + float: right; + display: none; + border: 0px; + outline: none; + margin-right: 3px; +} + +.mx_EventTile:hover .mx_EventTile_editButton { + display: inline-block; +} + +.mx_EventTile.menu .mx_EventTile_editButton { + display: inline-block; +} + +.mx_EventTile.menu .mx_MessageTimestamp { + display: inline-block; +} diff --git a/src/skins/vector/css/molecules/MatrixToolbar.css b/src/skins/vector/css/molecules/MatrixToolbar.css index 76845e4b..99c28240 100644 --- a/src/skins/vector/css/molecules/MatrixToolbar.css +++ b/src/skins/vector/css/molecules/MatrixToolbar.css @@ -15,7 +15,6 @@ limitations under the License. */ .mx_MatrixToolbar { - width: 100%; text-align: center; background-color: #ff0064; color: #fff; diff --git a/src/skins/vector/css/molecules/MemberTile.css b/src/skins/vector/css/molecules/MemberTile.css index a4310d20..faae142a 100644 --- a/src/skins/vector/css/molecules/MemberTile.css +++ b/src/skins/vector/css/molecules/MemberTile.css @@ -128,3 +128,7 @@ limitations under the License. .mx_MemberTile_zalgo { font-family: Helvetica, Arial, Sans-Serif; } + +.mx_MemberTile:hover .mx_MessageTimestamp { + display: block; +} diff --git a/src/skins/vector/css/molecules/RoomHeader.css b/src/skins/vector/css/molecules/RoomHeader.css index c0c8567d..f7adb618 100644 --- a/src/skins/vector/css/molecules/RoomHeader.css +++ b/src/skins/vector/css/molecules/RoomHeader.css @@ -116,13 +116,16 @@ limitations under the License. margin-top: -5px; } -.mx_RoomHeader_nameInput { +.mx_RoomHeader_name input, .mx_RoomHeader_nameInput { border-radius: 3px; width: 260px; border: 1px solid #c7c7c7; font-weight: 300; font-size: 14px; padding: 9px; +} + +.mx_RoomHeader_nameInput { margin-top: 6px; } @@ -160,3 +163,11 @@ limitations under the License. .mx_RoomHeader_button img { cursor: pointer; } + +.mx_RoomHeader_voipButton { + display: table-cell; +} + +.mx_RoomHeader_voipButtons { + margin-top: 18px; +} \ No newline at end of file diff --git a/src/skins/vector/css/molecules/RoomTile.css b/src/skins/vector/css/molecules/RoomTile.css index d43945c3..511fc94f 100644 --- a/src/skins/vector/css/molecules/RoomTile.css +++ b/src/skins/vector/css/molecules/RoomTile.css @@ -22,13 +22,13 @@ limitations under the License. .mx_RoomTile_avatar { display: table-cell; - padding-right: 12px; + padding-right: 10px; padding-top: 3px; padding-bottom: 3px; - padding-left: 16px; + padding-left: 10px; vertical-align: middle; - width: 40px; - height: 40px; + width: 36px; + height: 36px; position: relative; } @@ -45,6 +45,10 @@ limitations under the License. padding-right: 16px; } +.collapsed .mx_RoomTile_name { + display: none; +} + /* .mx_RoomTile_nameBadge { display: table; diff --git a/src/controllers/atoms/ImageView.js b/src/skins/vector/css/molecules/RoomTooltip.css similarity index 65% rename from src/controllers/atoms/ImageView.js rename to src/skins/vector/css/molecules/RoomTooltip.css index d0977e00..f45970fe 100644 --- a/src/controllers/atoms/ImageView.js +++ b/src/skins/vector/css/molecules/RoomTooltip.css @@ -14,7 +14,20 @@ See the License for the specific language governing permissions and limitations under the License. */ -'use strict'; +.mx_RoomTooltip { + display: none; + position: fixed; + border: 1px solid #a9dbf4; + border-radius: 8px; + background-color: #fff; + z-index: 1000; + margin-top: 6px; + left: 64px; + padding: 6px; +} -module.exports = { -}; +.mx_RoomTooltip_chevron { + position: absolute; + left: -9px; + top: 8px; +} diff --git a/src/skins/vector/css/organisms/LeftPanel.css b/src/skins/vector/css/organisms/LeftPanel.css index a00184dd..0e7e077e 100644 --- a/src/skins/vector/css/organisms/LeftPanel.css +++ b/src/skins/vector/css/organisms/LeftPanel.css @@ -29,7 +29,9 @@ limitations under the License. .mx_LeftPanel_hideButton { position: absolute; top: 10px; - right: 10px; + right: 0px; + padding: 8px; + cursor: pointer; } .mx_LeftPanel .mx_RoomList { @@ -39,7 +41,7 @@ limitations under the License. -webkit-order: 1; order: 1; - overflow-y: scroll; + overflow-y: auto; -webkit-flex: 1 1 0; flex: 1 1 0; } @@ -61,10 +63,6 @@ limitations under the License. color: #378bb4; } -.mx_LeftPanel .mx_BottomLeftMenu .mx_RoomTile_avatar { - padding-left: 14px; -} - .mx_LeftPanel .mx_BottomLeftMenu .mx_BottomLeftMenu_options { margin-top: 12px; width: 100%; diff --git a/src/skins/vector/css/organisms/RoomDirectory.css b/src/skins/vector/css/organisms/RoomDirectory.css index 793ba5af..21985a25 100644 --- a/src/skins/vector/css/organisms/RoomDirectory.css +++ b/src/skins/vector/css/organisms/RoomDirectory.css @@ -57,7 +57,7 @@ limitations under the License. } .mx_RoomDirectory_tableWrapper { - overflow-y: scroll; + overflow-y: auto; -webkit-flex: 1 1 0; flex: 1 1 0; } diff --git a/src/skins/vector/css/organisms/RoomList.css b/src/skins/vector/css/organisms/RoomList.css index 35978e5b..21cb7812 100644 --- a/src/skins/vector/css/organisms/RoomList.css +++ b/src/skins/vector/css/organisms/RoomList.css @@ -27,4 +27,5 @@ limitations under the License. .mx_RoomList h2 { padding-left: 16px; padding-right: 16px; + padding-bottom: 10px; } \ No newline at end of file diff --git a/src/skins/vector/css/organisms/RoomView.css b/src/skins/vector/css/organisms/RoomView.css index e1c589a2..2aab203a 100644 --- a/src/skins/vector/css/organisms/RoomView.css +++ b/src/skins/vector/css/organisms/RoomView.css @@ -106,10 +106,8 @@ limitations under the License. flex: 1 1 0; width: 100%; - margin-top: 18px; - margin-bottom: 18px; - overflow-y: scroll; + overflow-y: auto; } .mx_RoomView_messageListWrapper { @@ -123,6 +121,10 @@ limitations under the License. padding: 0px; } +.mx_RoomView_MessageList li { + clear: both; +} + .mx_RoomView_MessageList h2 { clear: both; margin-top: 32px; @@ -210,13 +212,37 @@ limitations under the License. .mx_RoomView_uploadProgressOuter { width: 100%; - background-color: black; - height: 5px; + background-color: rgba(169, 219, 244, 0.5); + height: 4px; } .mx_RoomView_uploadProgressInner { - background-color: blue; - height: 5px; + background-color: #80cef4; + height: 4px; +} + +.mx_RoomView_uploadFilename { + margin-top: 15px; + margin-left: 56px; +} + +.mx_RoomView_uploadIcon { + float: left; + margin-top: 6px; + margin-left: 5px; +} + +.mx_RoomView_uploadCancel { + float: right; + margin-top: 6px; + margin-right: 10px; +} + +.mx_RoomView_uploadBytes { + float: right; + opacity: 0.5; + margin-top: 15px; + margin-right: 10px; } .mx_RoomView_ongoingConfCallNotification { diff --git a/src/skins/vector/css/organisms/ViewSource.css b/src/skins/vector/css/organisms/ViewSource.css new file mode 100644 index 00000000..ae61ae58 --- /dev/null +++ b/src/skins/vector/css/organisms/ViewSource.css @@ -0,0 +1,3 @@ +.mx_ViewSource pre { + text-align: left; +} diff --git a/src/skins/vector/css/pages/MatrixChat.css b/src/skins/vector/css/pages/MatrixChat.css index 1c02d8cc..6fa0415a 100644 --- a/src/skins/vector/css/pages/MatrixChat.css +++ b/src/skins/vector/css/pages/MatrixChat.css @@ -73,6 +73,11 @@ limitations under the License. flex: 0 0 230px; } +.mx_MatrixChat .mx_LeftPanel.collapsed { + -webkit-flex: 0 0 60px; + flex: 0 0 60px; +} + .mx_MatrixChat .mx_MatrixChat_middlePanel { -webkit-box-ordinal-group: 2; -moz-box-ordinal-group: 2; @@ -83,7 +88,9 @@ limitations under the License. padding-left: 12px; padding-right: 12px; background-color: #f3f8fa; - width: 100%; + + -webkit-flex: 1; + flex: 1; /* XXX: Hack: apparently if you try to nest a flex-box * within a non-flex-box within a flex-box, the height @@ -111,3 +118,8 @@ limitations under the License. -webkit-flex: 0 0 230px; flex: 0 0 230px; } + +.mx_MatrixChat .mx_RightPanel.collapsed { + -webkit-flex: 0 0 72px; + flex: 0 0 72px; +} diff --git a/src/skins/vector/img/50e2c2.png b/src/skins/vector/img/50e2c2.png new file mode 100644 index 00000000..ee0f8558 Binary files /dev/null and b/src/skins/vector/img/50e2c2.png differ diff --git a/src/skins/vector/img/80cef4.png b/src/skins/vector/img/80cef4.png new file mode 100644 index 00000000..637d03f6 Binary files /dev/null and b/src/skins/vector/img/80cef4.png differ diff --git a/src/skins/vector/img/cancel.png b/src/skins/vector/img/cancel.png new file mode 100644 index 00000000..c963a4b7 Binary files /dev/null and b/src/skins/vector/img/cancel.png differ diff --git a/src/skins/vector/img/create-big.png b/src/skins/vector/img/create-big.png index 9c4627b1..247b0030 100644 Binary files a/src/skins/vector/img/create-big.png and b/src/skins/vector/img/create-big.png differ diff --git a/src/skins/vector/img/directory-big.png b/src/skins/vector/img/directory-big.png index fb2bc388..bbcb16a4 100644 Binary files a/src/skins/vector/img/directory-big.png and b/src/skins/vector/img/directory-big.png differ diff --git a/src/skins/vector/img/f4c371.png b/src/skins/vector/img/f4c371.png new file mode 100644 index 00000000..ad3b8f16 Binary files /dev/null and b/src/skins/vector/img/f4c371.png differ diff --git a/src/skins/vector/img/fileicon.png b/src/skins/vector/img/fileicon.png new file mode 100644 index 00000000..be277a1c Binary files /dev/null and b/src/skins/vector/img/fileicon.png differ diff --git a/src/skins/vector/img/hide.png b/src/skins/vector/img/hide.png index 3e64618b..c5aaf0dd 100644 Binary files a/src/skins/vector/img/hide.png and b/src/skins/vector/img/hide.png differ diff --git a/src/skins/vector/img/menu.png b/src/skins/vector/img/menu.png old mode 100644 new mode 100755 index 3550878b..b45f8895 Binary files a/src/skins/vector/img/menu.png and b/src/skins/vector/img/menu.png differ diff --git a/src/skins/vector/img/settings-big.png b/src/skins/vector/img/settings-big.png index 663ca163..3be13bc7 100644 Binary files a/src/skins/vector/img/settings-big.png and b/src/skins/vector/img/settings-big.png differ diff --git a/src/skins/vector/img/spinner.gif b/src/skins/vector/img/spinner.gif new file mode 100644 index 00000000..ab487121 Binary files /dev/null and b/src/skins/vector/img/spinner.gif differ diff --git a/src/skins/vector/img/zoom.png b/src/skins/vector/img/zoom.png new file mode 100644 index 00000000..f05ea959 Binary files /dev/null and b/src/skins/vector/img/zoom.png differ diff --git a/src/skins/vector/skindex.js b/src/skins/vector/skindex.js index 4f76ad62..8dba10cf 100644 --- a/src/skins/vector/skindex.js +++ b/src/skins/vector/skindex.js @@ -23,6 +23,9 @@ limitations under the License. var skin = {}; +skin['atoms.create_room.CreateRoomButton'] = require('./views/atoms/create_room/CreateRoomButton'); +skin['atoms.create_room.Presets'] = require('./views/atoms/create_room/Presets'); +skin['atoms.create_room.RoomAlias'] = require('./views/atoms/create_room/RoomAlias'); skin['atoms.EditableText'] = require('./views/atoms/EditableText'); skin['atoms.EnableNotificationsButton'] = require('./views/atoms/EnableNotificationsButton'); skin['atoms.ImageView'] = require('./views/atoms/ImageView'); @@ -30,32 +33,34 @@ skin['atoms.LogoutButton'] = require('./views/atoms/LogoutButton'); skin['atoms.MemberAvatar'] = require('./views/atoms/MemberAvatar'); skin['atoms.MessageTimestamp'] = require('./views/atoms/MessageTimestamp'); skin['atoms.RoomAvatar'] = require('./views/atoms/RoomAvatar'); -skin['atoms.create_room.CreateRoomButton'] = require('./views/atoms/create_room/CreateRoomButton'); -skin['atoms.create_room.Presets'] = require('./views/atoms/create_room/Presets'); -skin['atoms.create_room.RoomAlias'] = require('./views/atoms/create_room/RoomAlias'); skin['atoms.voip.VideoFeed'] = require('./views/atoms/voip/VideoFeed'); skin['molecules.BottomLeftMenu'] = require('./views/molecules/BottomLeftMenu'); +skin['molecules.BottomLeftMenuTile'] = require('./views/molecules/BottomLeftMenuTile'); skin['molecules.ChangeAvatar'] = require('./views/molecules/ChangeAvatar'); +skin['molecules.ChangeDisplayName'] = require('./views/molecules/ChangeDisplayName'); skin['molecules.ChangePassword'] = require('./views/molecules/ChangePassword'); skin['molecules.DateSeparator'] = require('./views/molecules/DateSeparator'); skin['molecules.EventAsTextTile'] = require('./views/molecules/EventAsTextTile'); +skin['molecules.EventTile'] = require('./views/molecules/EventTile'); +skin['molecules.MatrixToolbar'] = require('./views/molecules/MatrixToolbar'); +skin['molecules.MemberInfo'] = require('./views/molecules/MemberInfo'); +skin['molecules.MemberTile'] = require('./views/molecules/MemberTile'); skin['molecules.MEmoteTile'] = require('./views/molecules/MEmoteTile'); +skin['molecules.MessageComposer'] = require('./views/molecules/MessageComposer'); +skin['molecules.MessageContextMenu'] = require('./views/molecules/MessageContextMenu'); +skin['molecules.MessageTile'] = require('./views/molecules/MessageTile'); skin['molecules.MFileTile'] = require('./views/molecules/MFileTile'); skin['molecules.MImageTile'] = require('./views/molecules/MImageTile'); skin['molecules.MNoticeTile'] = require('./views/molecules/MNoticeTile'); skin['molecules.MRoomMemberTile'] = require('./views/molecules/MRoomMemberTile'); skin['molecules.MTextTile'] = require('./views/molecules/MTextTile'); -skin['molecules.MatrixToolbar'] = require('./views/molecules/MatrixToolbar'); -skin['molecules.MemberInfo'] = require('./views/molecules/MemberInfo'); -skin['molecules.MemberTile'] = require('./views/molecules/MemberTile'); -skin['molecules.MessageComposer'] = require('./views/molecules/MessageComposer'); -skin['molecules.MessageTile'] = require('./views/molecules/MessageTile'); skin['molecules.ProgressBar'] = require('./views/molecules/ProgressBar'); skin['molecules.RoomCreate'] = require('./views/molecules/RoomCreate'); skin['molecules.RoomDropTarget'] = require('./views/molecules/RoomDropTarget'); skin['molecules.RoomHeader'] = require('./views/molecules/RoomHeader'); skin['molecules.RoomSettings'] = require('./views/molecules/RoomSettings'); skin['molecules.RoomTile'] = require('./views/molecules/RoomTile'); +skin['molecules.RoomTooltip'] = require('./views/molecules/RoomTooltip'); skin['molecules.SenderProfile'] = require('./views/molecules/SenderProfile'); skin['molecules.ServerConfig'] = require('./views/molecules/ServerConfig'); skin['molecules.UnknownMessageTile'] = require('./views/molecules/UnknownMessageTile'); @@ -63,6 +68,7 @@ skin['molecules.UserSelector'] = require('./views/molecules/UserSelector'); skin['molecules.voip.CallView'] = require('./views/molecules/voip/CallView'); skin['molecules.voip.IncomingCallBox'] = require('./views/molecules/voip/IncomingCallBox'); skin['molecules.voip.VideoView'] = require('./views/molecules/voip/VideoView'); +skin['organisms.CasLogin'] = require('./views/organisms/CasLogin'); skin['organisms.CreateRoom'] = require('./views/organisms/CreateRoom'); skin['organisms.ErrorDialog'] = require('./views/organisms/ErrorDialog'); skin['organisms.LeftPanel'] = require('./views/organisms/LeftPanel'); @@ -75,6 +81,7 @@ skin['organisms.RoomDirectory'] = require('./views/organisms/RoomDirectory'); skin['organisms.RoomList'] = require('./views/organisms/RoomList'); skin['organisms.RoomView'] = require('./views/organisms/RoomView'); skin['organisms.UserSettings'] = require('./views/organisms/UserSettings'); +skin['organisms.ViewSource'] = require('./views/organisms/ViewSource'); skin['pages.MatrixChat'] = require('./views/pages/MatrixChat'); skin['templates.Login'] = require('./views/templates/Login'); skin['templates.Register'] = require('./views/templates/Register'); diff --git a/src/skins/vector/views/atoms/ImageView.js b/src/skins/vector/views/atoms/ImageView.js index a0d69bcc..676348c0 100644 --- a/src/skins/vector/views/atoms/ImageView.js +++ b/src/skins/vector/views/atoms/ImageView.js @@ -18,11 +18,8 @@ limitations under the License. var React = require('react'); -var ImageViewController = require('../../../../controllers/atoms/ImageView') - module.exports = React.createClass({ displayName: 'ImageView', - mixins: [ImageViewController], // XXX: keyboard shortcuts for managing dialogs should be done by the modal dialog base class omehow, surely... componentDidMount: function() { diff --git a/src/skins/vector/views/atoms/MessageTimestamp.js b/src/skins/vector/views/atoms/MessageTimestamp.js index ef866afa..98cfe4a1 100644 --- a/src/skins/vector/views/atoms/MessageTimestamp.js +++ b/src/skins/vector/views/atoms/MessageTimestamp.js @@ -18,17 +18,39 @@ limitations under the License. var React = require('react'); -var MessageTimestampController = require('matrix-react-sdk/lib/controllers/atoms/MessageTimestamp') +var days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; +var months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; module.exports = React.createClass({ displayName: 'MessageTimestamp', - mixins: [MessageTimestampController], + + formatDate: function(date) { + // date.toLocaleTimeString is completely system dependent. + // just go 24h for now + function pad(n) { + return (n < 10 ? '0' : '') + n; + } + + var now = new Date(); + if (date.toDateString() === now.toDateString()) { + return pad(date.getHours()) + ':' + pad(date.getMinutes()); + } + else if (now.getTime() - date.getTime() < 6 * 24 * 60 * 60 * 1000) { + return days[date.getDay()] + " " + pad(date.getHours()) + ':' + pad(date.getMinutes()); + } + else if (now.getFullYear() === date.getFullYear()) { + return days[date.getDay()] + ", " + months[date.getMonth()] + " " + (date.getDay()+1) + " " + pad(date.getHours()) + ':' + pad(date.getMinutes()); + } + else { + return days[date.getDay()] + ", " + months[date.getMonth()] + " " + (date.getDay()+1) + " " + date.getFullYear() + " " + pad(date.getHours()) + ':' + pad(date.getMinutes()); + } + }, render: function() { var date = new Date(this.props.ts); return ( - {date.toLocaleTimeString()} + { this.formatDate(date) } ); }, diff --git a/src/skins/vector/views/atoms/RoomAvatar.js b/src/skins/vector/views/atoms/RoomAvatar.js index ec2bf5ec..3b5d4634 100644 --- a/src/skins/vector/views/atoms/RoomAvatar.js +++ b/src/skins/vector/views/atoms/RoomAvatar.js @@ -24,10 +24,31 @@ module.exports = React.createClass({ displayName: 'RoomAvatar', mixins: [RoomAvatarController], + getUrlList: function() { + return [ + this.roomAvatarUrl(), + this.getOneToOneAvatar(), + this.getFallbackAvatar() + ]; + }, + + getFallbackAvatar: function() { + var images = [ '80cef4', '50e2c2', 'f4c371' ]; + var total = 0; + for (var i = 0; i < this.props.room.roomId.length; ++i) { + total += this.props.room.roomId.charCodeAt(i); + } + return 'img/' + images[total % images.length] + '.png'; + }, + render: function() { + var style = { + maxWidth: this.props.width, + maxHeight: this.props.height, + }; return ( ); } diff --git a/src/skins/vector/views/atoms/Spinner.js b/src/skins/vector/views/atoms/Spinner.js new file mode 100644 index 00000000..908f2678 --- /dev/null +++ b/src/skins/vector/views/atoms/Spinner.js @@ -0,0 +1,34 @@ +/* +Copyright 2015 OpenMarket Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +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. +*/ + +'use strict'; + +var React = require('react'); + +module.exports = React.createClass({ + displayName: 'Spinner', + + render: function() { + var w = this.props.w || 32; + var h = this.props.h || 32; + var imgClass = this.props.imgClassName || ""; + return ( +
    + +
    + ); + } +}); diff --git a/src/skins/vector/views/atoms/voip/VideoFeed.js b/src/skins/vector/views/atoms/voip/VideoFeed.js index 748a431a..9cf28d1b 100644 --- a/src/skins/vector/views/atoms/voip/VideoFeed.js +++ b/src/skins/vector/views/atoms/voip/VideoFeed.js @@ -18,11 +18,8 @@ limitations under the License. var React = require('react'); -var VideoFeedController = require('matrix-react-sdk/lib/controllers/atoms/voip/VideoFeed') - module.exports = React.createClass({ displayName: 'VideoFeed', - mixins: [VideoFeedController], render: function() { return ( diff --git a/src/skins/vector/views/molecules/BottomLeftMenu.js b/src/skins/vector/views/molecules/BottomLeftMenu.js index 809da0ee..2020d29d 100644 --- a/src/skins/vector/views/molecules/BottomLeftMenu.js +++ b/src/skins/vector/views/molecules/BottomLeftMenu.js @@ -17,7 +17,7 @@ limitations under the License. 'use strict'; var React = require('react'); - +var sdk = require('matrix-react-sdk') var dis = require('matrix-react-sdk/lib/dispatcher'); module.exports = React.createClass({ @@ -35,28 +35,24 @@ module.exports = React.createClass({ dis.dispatch({action: 'view_create_room'}); }, + getLabel: function(name) { + if (!this.props.collapsed) { + return
    {name}
    + } + else if (this.state.hover) { + var RoomTooltip = sdk.getComponent("molecules.RoomTooltip"); + return ; + } + }, + render: function() { + var BottomLeftMenuTile = sdk.getComponent('molecules.BottomLeftMenuTile'); return (
    -
    -
    - Create new room -
    -
    Create new room
    -
    -
    -
    - Directory -
    -
    Directory
    -
    -
    -
    - Settings -
    -
    Settings
    -
    + + +
    ); diff --git a/src/skins/vector/views/molecules/BottomLeftMenuTile.js b/src/skins/vector/views/molecules/BottomLeftMenuTile.js new file mode 100644 index 00000000..2644769c --- /dev/null +++ b/src/skins/vector/views/molecules/BottomLeftMenuTile.js @@ -0,0 +1,57 @@ +/* +Copyright 2015 OpenMarket Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +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. +*/ + +'use strict'; + +var React = require('react'); + +var sdk = require('matrix-react-sdk') + +module.exports = React.createClass({ + displayName: 'BottomLeftMenuTile', + + getInitialState: function() { + return( { hover : false }); + }, + + onMouseEnter: function() { + this.setState( { hover : true }); + }, + + onMouseLeave: function() { + this.setState( { hover : false }); + }, + + render: function() { + var label; + if (!this.props.collapsed) { + label =
    { this.props.label }
    ; + } + else if (this.state.hover) { + var RoomTooltip = sdk.getComponent("molecules.RoomTooltip"); + label = ; + } + + return ( +
    +
    + +
    + { label } +
    + ); + } +}); diff --git a/src/skins/vector/views/molecules/ChangeAvatar.js b/src/skins/vector/views/molecules/ChangeAvatar.js index 8fafacc8..42c2d1fd 100644 --- a/src/skins/vector/views/molecules/ChangeAvatar.js +++ b/src/skins/vector/views/molecules/ChangeAvatar.js @@ -18,6 +18,7 @@ limitations under the License. var React = require('react'); +var sdk = require('matrix-react-sdk') var ChangeAvatarController = require('matrix-react-sdk/lib/controllers/molecules/ChangeAvatar') var Loader = require("react-loader"); @@ -28,6 +29,7 @@ module.exports = React.createClass({ mixins: [ChangeAvatarController], onFileSelected: function(ev) { + this.avatarSet = true; this.setAvatarFromFile(ev.target.files[0]); }, @@ -38,22 +40,33 @@ module.exports = React.createClass({ }, render: function() { + var RoomAvatar = sdk.getComponent('atoms.RoomAvatar'); + var avatarImg; + // Having just set an avatar we just display that since it will take a little + // time to propagate through to the RoomAvatar. + if (this.props.room && !this.avatarSet) { + avatarImg = ; + } else { + var style = { + maxWidth: 320, + maxHeight: 240, + }; + avatarImg = ; + } + switch (this.state.phase) { case this.Phases.Display: case this.Phases.Error: return (
    - + {avatarImg}
    Upload new: {this.state.errorText}
    -
    - -
    ); case this.Phases.Uploading: diff --git a/src/skins/vector/views/molecules/ChangeDisplayName.js b/src/skins/vector/views/molecules/ChangeDisplayName.js new file mode 100644 index 00000000..1a094ec2 --- /dev/null +++ b/src/skins/vector/views/molecules/ChangeDisplayName.js @@ -0,0 +1,56 @@ +/* +Copyright 2015 OpenMarket Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +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. +*/ + +'use strict'; + +var React = require('react'); +var sdk = require('matrix-react-sdk'); + +var ChangeDisplayNameController = require("matrix-react-sdk/lib/controllers/molecules/ChangeDisplayName"); +var Loader = require("react-loader"); + + +module.exports = React.createClass({ + displayName: 'ChangeDisplayName', + mixins: [ChangeDisplayNameController], + + edit: function() { + this.refs.displayname_edit.edit() + }, + + onValueChanged: function(new_value, shouldSubmit) { + if (shouldSubmit) { + this.changeDisplayname(new_value); + } + }, + + render: function() { + if (this.state.busy) { + return ( + + ); + } else if (this.state.errorString) { + return ( +
    {this.state.errorString}
    + ); + } else { + var EditableText = sdk.getComponent('atoms.EditableText'); + return ( + + ); + } + } +}); diff --git a/src/skins/vector/views/molecules/EventAsTextTile.js b/src/skins/vector/views/molecules/EventAsTextTile.js index e8beddf2..ec644a4e 100644 --- a/src/skins/vector/views/molecules/EventAsTextTile.js +++ b/src/skins/vector/views/molecules/EventAsTextTile.js @@ -18,33 +18,24 @@ limitations under the License. var React = require('react'); -var EventAsTextTileController = require('matrix-react-sdk/lib/controllers/molecules/EventAsTextTile') -var sdk = require('matrix-react-sdk') var TextForEvent = require('matrix-react-sdk/lib/TextForEvent'); module.exports = React.createClass({ displayName: 'EventAsTextTile', - mixins: [EventAsTextTileController], + + statics: { + needsSenderProfile: function() { + return false; + } + }, render: function() { - var MessageTimestamp = sdk.getComponent('atoms.MessageTimestamp'); - var MemberAvatar = sdk.getComponent('atoms.MemberAvatar'); - var text = TextForEvent.textForEvent(this.props.mxEvent); if (text == null || text.length == 0) return null; - var timestamp = this.props.last ? : null; - var avatar = this.props.mxEvent.sender ? : null; return ( -
    -
    - { avatar } -
    - { timestamp } - - - {TextForEvent.textForEvent(this.props.mxEvent)} - +
    + {TextForEvent.textForEvent(this.props.mxEvent)}
    ); }, diff --git a/src/skins/vector/views/molecules/EventTile.js b/src/skins/vector/views/molecules/EventTile.js new file mode 100644 index 00000000..389b8b40 --- /dev/null +++ b/src/skins/vector/views/molecules/EventTile.js @@ -0,0 +1,131 @@ +/* +Copyright 2015 OpenMarket Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +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. +*/ + +'use strict'; + +var React = require('react'); +var classNames = require("classnames"); + +var sdk = require('matrix-react-sdk') + +var EventTileController = require('matrix-react-sdk/lib/controllers/molecules/EventTile') +var ContextualMenu = require('../../../../ContextualMenu'); + +var eventTileTypes = { + 'm.room.message': 'molecules.MessageTile', + 'm.room.member' : 'molecules.EventAsTextTile', + 'm.call.invite' : 'molecules.EventAsTextTile', + 'm.call.answer' : 'molecules.EventAsTextTile', + 'm.call.hangup' : 'molecules.EventAsTextTile', + 'm.room.topic' : 'molecules.EventAsTextTile', +}; + +module.exports = React.createClass({ + displayName: 'EventTile', + mixins: [EventTileController], + + statics: { + supportsEventType: function(et) { + return eventTileTypes[et] !== undefined; + } + }, + + getInitialState: function() { + return {menu: false}; + }, + + onEditClicked: function(e) { + var MessageContextMenu = sdk.getComponent('molecules.MessageContextMenu'); + var buttonRect = e.target.getBoundingClientRect() + var x = buttonRect.right; + var y = buttonRect.top + (e.target.height / 2); + var self = this; + ContextualMenu.createMenu(MessageContextMenu, { + mxEvent: this.props.mxEvent, + left: x, + top: y, + onFinished: function() { + self.setState({menu: false}); + } + }); + this.setState({menu: true}); + }, + + render: function() { + var MessageTimestamp = sdk.getComponent('atoms.MessageTimestamp'); + var SenderProfile = sdk.getComponent('molecules.SenderProfile'); + var MemberAvatar = sdk.getComponent('atoms.MemberAvatar'); + + var content = this.props.mxEvent.getContent(); + var msgtype = content.msgtype; + + var EventTileType = sdk.getComponent(eventTileTypes[this.props.mxEvent.getType()]); + // This shouldn't happen: the caller should check we support this type + // before trying to instantiate us + if (!EventTileType) { + return null; + } + + var classes = classNames({ + mx_EventTile: true, + mx_EventTile_sending: ['sending', 'queued'].indexOf( + this.props.mxEvent.status + ) !== -1, + mx_EventTile_notSent: this.props.mxEvent.status == 'not_sent', + mx_EventTile_highlight: this.shouldHighlight(), + mx_EventTile_continuation: this.props.continuation, + mx_EventTile_last: this.props.last, + menu: this.state.menu + }); + var timestamp = + var editButton = ( + + ); + + var aux = null; + if (msgtype === 'm.image') aux = "sent an image"; + else if (msgtype === 'm.video') aux = "sent a video"; + else if (msgtype === 'm.file') aux = "uploaded a file"; + + var avatar, sender; + if (!this.props.continuation) { + if (this.props.mxEvent.sender) { + avatar = ( +
    + +
    + ); + } + if (EventTileType.needsSenderProfile()) { + sender = ; + } + } + return ( +
    + { avatar } + { sender } +
    + { timestamp } + { editButton } + +
    +
    + ); + }, +}); diff --git a/src/skins/vector/views/molecules/MImageTile.js b/src/skins/vector/views/molecules/MImageTile.js index 59d9cfde..ed61a390 100644 --- a/src/skins/vector/views/molecules/MImageTile.js +++ b/src/skins/vector/views/molecules/MImageTile.js @@ -19,15 +19,12 @@ limitations under the License. var React = require('react'); var filesize = require('filesize'); -var MImageTileController = require('matrix-react-sdk/lib/controllers/molecules/MImageTile') - var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg'); var Modal = require('matrix-react-sdk/lib/Modal'); var sdk = require('matrix-react-sdk') module.exports = React.createClass({ displayName: 'MImageTile', - mixins: [MImageTileController], thumbHeight: function(fullWidth, fullHeight, thumbWidth, thumbHeight) { if (!fullWidth || !fullHeight) { diff --git a/src/skins/vector/views/molecules/MRoomMemberTile.js b/src/skins/vector/views/molecules/MRoomMemberTile.js index 4e163e1d..0048306d 100644 --- a/src/skins/vector/views/molecules/MRoomMemberTile.js +++ b/src/skins/vector/views/molecules/MRoomMemberTile.js @@ -18,14 +18,11 @@ limitations under the License. var React = require('react'); -var MRoomMemberTileController = require('matrix-react-sdk/lib/controllers/molecules/MRoomMemberTile') - var sdk = require('matrix-react-sdk') var TextForEvent = require('matrix-react-sdk/lib/TextForEvent'); module.exports = React.createClass({ displayName: 'MRoomMemberTile', - mixins: [MRoomMemberTileController], getMemberEventText: function() { return TextForEvent.textForEvent(this.props.mxEvent); diff --git a/src/skins/vector/views/molecules/MatrixToolbar.js b/src/skins/vector/views/molecules/MatrixToolbar.js index 0b6c58e0..4a299f14 100644 --- a/src/skins/vector/views/molecules/MatrixToolbar.js +++ b/src/skins/vector/views/molecules/MatrixToolbar.js @@ -20,11 +20,8 @@ var React = require('react'); var sdk = require('matrix-react-sdk') -var MatrixToolbarController = require('matrix-react-sdk/lib/controllers/molecules/MatrixToolbar') - module.exports = React.createClass({ displayName: 'MatrixToolbar', - mixins: [MatrixToolbarController], hideToolbar: function() { var Notifier = sdk.getComponent('organisms.Notifier'); diff --git a/src/skins/vector/views/molecules/MemberInfo.js b/src/skins/vector/views/molecules/MemberInfo.js index cc9a8f2d..a2a3874a 100644 --- a/src/skins/vector/views/molecules/MemberInfo.js +++ b/src/skins/vector/views/molecules/MemberInfo.js @@ -17,6 +17,7 @@ limitations under the License. 'use strict'; var React = require('react'); +var Loader = require("../atoms/Spinner"); var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg'); var MemberInfoController = require('matrix-react-sdk/lib/controllers/molecules/MemberInfo') @@ -26,7 +27,7 @@ module.exports = React.createClass({ mixins: [MemberInfoController], render: function() { - var interactButton, kickButton, banButton, muteButton, giveModButton; + var interactButton, kickButton, banButton, muteButton, giveModButton, spinner; if (this.props.member.userId === MatrixClientPeg.get().credentials.userId) { interactButton =
    Leave room
    ; } @@ -34,6 +35,10 @@ module.exports = React.createClass({ interactButton =
    Start chat
    ; } + if (this.state.creatingRoom) { + spinner = ; + } + if (this.state.can.kick) { kickButton =
    Kick @@ -64,6 +69,7 @@ module.exports = React.createClass({ {kickButton} {banButton} {giveModButton} + {spinner}
    ); } diff --git a/src/skins/vector/views/molecules/MemberTile.js b/src/skins/vector/views/molecules/MemberTile.js index 991616d5..4c34fcb7 100644 --- a/src/skins/vector/views/molecules/MemberTile.js +++ b/src/skins/vector/views/molecules/MemberTile.js @@ -31,6 +31,24 @@ module.exports = React.createClass({ displayName: 'MemberTile', mixins: [MemberTileController], + shouldComponentUpdate: function(nextProps, nextState) { + if (this.state.hover !== nextState.hover) return true; + if ( + this.member_last_modified_time === undefined || + this.member_last_modified_time < nextProps.member.getLastModifiedTime() + ) { + return true + } + if ( + nextProps.member.user && + (this.user_last_modified_time === undefined || + this.user_last_modified_time < nextProps.member.user.getLastModifiedTime()) + ) { + return true + } + return false; + }, + mouseEnter: function(e) { this.setState({ 'hover': true }); }, @@ -93,6 +111,11 @@ module.exports = React.createClass({ }, render: function() { + this.member_last_modified_time = this.props.member.getLastModifiedTime(); + if (this.props.member.user) { + this.user_last_modified_time = this.props.member.user.getLastModifiedTime(); + } + var isMyUser = MatrixClientPeg.get().credentials.userId == this.props.member.userId; var power; diff --git a/src/skins/vector/views/molecules/MessageContextMenu.js b/src/skins/vector/views/molecules/MessageContextMenu.js new file mode 100644 index 00000000..249d9a34 --- /dev/null +++ b/src/skins/vector/views/molecules/MessageContextMenu.js @@ -0,0 +1,105 @@ +/* +Copyright 2015 OpenMarket Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +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. +*/ + +'use strict'; + +var React = require('react'); + +var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg'); +var dis = require('matrix-react-sdk/lib/dispatcher'); +var sdk = require('matrix-react-sdk') +var Modal = require('matrix-react-sdk/lib/Modal'); + +module.exports = React.createClass({ + displayName: 'MessageContextMenu', + + onResendClick: function() { + MatrixClientPeg.get().resendEvent( + this.props.mxEvent, MatrixClientPeg.get().getRoom( + this.props.mxEvent.getRoomId() + ) + ).done(function() { + dis.dispatch({ + action: 'message_sent' + }); + }, function() { + dis.dispatch({ + action: 'message_send_failed' + }); + }); + dis.dispatch({action: 'message_resend_started'}); + if (this.props.onFinished) this.props.onFinished(); + }, + + onViewSourceClick: function() { + var ViewSource = sdk.getComponent('organisms.ViewSource'); + Modal.createDialog(ViewSource, { + mxEvent: this.props.mxEvent + }); + if (this.props.onFinished) this.props.onFinished(); + }, + + onRedactClick: function() { + MatrixClientPeg.get().redactEvent( + this.props.mxEvent.getRoomId(), this.props.mxEvent.getId() + ).done(function() { + // message should disappear by itself + }, function(e) { + var ErrorDialog = sdk.getComponent("organisms.ErrorDialog"); + // display error message stating you couldn't delete this. + var code = e.errcode || e.statusCode; + Modal.createDialog(ErrorDialog, { + title: "Error", + description: "You cannot delete this message. (" + code + ")" + }); + }); + if (this.props.onFinished) this.props.onFinished(); + }, + + render: function() { + var resendButton; + var viewSourceButton; + var redactButton; + + if (this.props.mxEvent.status == 'not_sent') { + resendButton = ( +
    + Resend +
    + ); + } + else { + redactButton = ( +
    + Delete +
    + ); + } + viewSourceButton = ( +
    + View Source +
    + ); + + return ( +
    + {resendButton} + {redactButton} + {viewSourceButton} +
    + ); + } +}); diff --git a/src/skins/vector/views/molecules/MessageTile.js b/src/skins/vector/views/molecules/MessageTile.js index 386b19d3..f30fee92 100644 --- a/src/skins/vector/views/molecules/MessageTile.js +++ b/src/skins/vector/views/molecules/MessageTile.js @@ -18,8 +18,6 @@ limitations under the License. var React = require('react'); -var classNames = require("classnames"); - var sdk = require('matrix-react-sdk') var MessageTileController = require('matrix-react-sdk/lib/controllers/molecules/MessageTile') @@ -28,11 +26,13 @@ module.exports = React.createClass({ displayName: 'MessageTile', mixins: [MessageTileController], - render: function() { - var MessageTimestamp = sdk.getComponent('atoms.MessageTimestamp'); - var SenderProfile = sdk.getComponent('molecules.SenderProfile'); - var MemberAvatar = sdk.getComponent('atoms.MemberAvatar'); + statics: { + needsSenderProfile: function() { + return true; + } + }, + render: function() { var UnknownMessageTile = sdk.getComponent('molecules.UnknownMessageTile'); var tileTypes = { @@ -49,47 +49,7 @@ module.exports = React.createClass({ if (msgtype && tileTypes[msgtype]) { TileType = tileTypes[msgtype]; } - var classes = classNames({ - mx_MessageTile: true, - mx_MessageTile_sending: ['sending', 'queued'].indexOf( - this.props.mxEvent.status - ) !== -1, - mx_MessageTile_notSent: this.props.mxEvent.status == 'not_sent', - mx_MessageTile_highlight: this.shouldHighlight(), - mx_MessageTile_continuation: this.props.continuation, - mx_MessageTile_last: this.props.last, - }); - var timestamp = - var aux = null; - if (msgtype === 'm.image') aux = "sent an image"; - else if (msgtype === 'm.video') aux = "sent a video"; - else if (msgtype === 'm.file') aux = "uploaded a file"; - - var avatar, sender, resend; - if (!this.props.continuation) { - if (this.props.mxEvent.sender) { - avatar = ( -
    - -
    - ); - } - sender = ; - } - if (this.props.mxEvent.status === "not_sent" && !this.state.resending) { - resend = ; - } - return ( -
    - { avatar } - { timestamp } - { resend } - { sender } - -
    - ); + return ; }, }); diff --git a/src/skins/vector/views/molecules/RoomHeader.js b/src/skins/vector/views/molecules/RoomHeader.js index f7d3fe7e..d3f9119a 100644 --- a/src/skins/vector/views/molecules/RoomHeader.js +++ b/src/skins/vector/views/molecules/RoomHeader.js @@ -18,7 +18,9 @@ limitations under the License. var React = require('react'); var sdk = require('matrix-react-sdk') +var dis = require('matrix-react-sdk/lib/dispatcher') +var CallHandler = require('matrix-react-sdk/lib/CallHandler'); var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg'); var RoomHeaderController = require('matrix-react-sdk/lib/controllers/molecules/RoomHeader') @@ -36,6 +38,10 @@ module.exports = React.createClass({ return this.refs.name_edit.getDOMNode().value; }, + onFullscreenClick: function() { + dis.dispatch({action: 'video_fullscreen', fullscreen: true}, true); + }, + render: function() { var EditableText = sdk.getComponent("atoms.EditableText"); var RoomAvatar = sdk.getComponent('atoms.RoomAvatar'); @@ -52,18 +58,41 @@ module.exports = React.createClass({ else { var topic = this.props.room.currentState.getStateEvents('m.room.topic', ''); - var callButtons; - if (this.state) { - switch (this.state.call_state) { - case "ringback": - case "connected": - callButtons = ( -
    - End call -
    - ); - break; + var call_buttons; + var zoom_button; + if (this.state && this.state.call_state != 'ended') { + //var muteVideoButton; + var activeCall = ( + CallHandler.getCallForRoom(this.props.room.roomId) + ); +/* + if (activeCall && activeCall.type === "video") { + muteVideoButton = ( +
    + { + (activeCall.isLocalVideoMuted() ? + "Unmute" : "Mute") + " video" + } +
    + ); } + {muteVideoButton} +
    + { + (activeCall && activeCall.isMicrophoneMuted() ? + "Unmute" : "Mute") + " audio" + } +
    +*/ + + call_buttons = ( +
    + End call +
    + ); } var name = null; @@ -97,7 +126,15 @@ module.exports = React.createClass({ var roomAvatar = null; if (this.props.room) { roomAvatar = ( - + + ); + } + + if (activeCall && activeCall.type == "video") { + zoom_button = ( +
    + Fullscreen +
    ); } @@ -112,18 +149,19 @@ module.exports = React.createClass({ { topic_el }
    - {callButtons} + {call_buttons} {cancel_button} {save_button}
    { settings_button } + { zoom_button }
    Search
    -
    +
    Video call
    -
    +
    VoIP call
    diff --git a/src/skins/vector/views/molecules/RoomSettings.js b/src/skins/vector/views/molecules/RoomSettings.js index d6d36a13..c5e08ff9 100644 --- a/src/skins/vector/views/molecules/RoomSettings.js +++ b/src/skins/vector/views/molecules/RoomSettings.js @@ -18,6 +18,7 @@ limitations under the License. var React = require('react'); var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg'); +var sdk = require('matrix-react-sdk'); var RoomSettingsController = require('matrix-react-sdk/lib/controllers/molecules/RoomSettings') @@ -65,6 +66,8 @@ module.exports = React.createClass({ }, render: function() { + var ChangeAvatar = sdk.getComponent('molecules.ChangeAvatar'); + var topic = this.props.room.currentState.getStateEvents('m.room.topic', ''); if (topic) topic = topic.getContent().topic; @@ -76,6 +79,8 @@ module.exports = React.createClass({ var power_levels = this.props.room.currentState.getStateEvents('m.room.power_levels', ''); + var events_levels = power_levels.events || {}; + if (power_levels) { power_levels = power_levels.getContent(); @@ -91,8 +96,7 @@ module.exports = React.createClass({ if (power_levels.kick == undefined) kick_level = 50; if (power_levels.redact == undefined) redact_level = 50; - var user_levels = power_levels.users || []; - var events_levels = power_levels.events || []; + var user_levels = power_levels.users || {}; var user_id = MatrixClientPeg.get().credentials.userId; @@ -124,7 +128,21 @@ module.exports = React.createClass({ var can_change_levels = false; } - var banned = this.props.room.getMembersWithMemership("ban"); + var room_avatar_level = parseInt(power_levels.state_default || 0); + if (events_levels['m.room.avatar'] !== undefined) { + room_avatar_level = events_levels['m.room.avatar']; + } + var can_set_room_avatar = current_user_level >= room_avatar_level; + + var change_avatar; + if (can_set_room_avatar) { + change_avatar =
    +

    Room Icon

    + +
    ; + } + + var banned = this.props.room.getMembersWithMembership("ban"); return (
    @@ -207,6 +225,7 @@ module.exports = React.createClass({ ); })}
    + {change_avatar}
    ); } diff --git a/src/skins/vector/views/molecules/RoomTile.js b/src/skins/vector/views/molecules/RoomTile.js index 61fa0021..39e7b43c 100644 --- a/src/skins/vector/views/molecules/RoomTile.js +++ b/src/skins/vector/views/molecules/RoomTile.js @@ -28,6 +28,19 @@ var sdk = require('matrix-react-sdk') module.exports = React.createClass({ displayName: 'RoomTile', mixins: [RoomTileController], + + getInitialState: function() { + return( { hover : false }); + }, + + onMouseEnter: function() { + this.setState( { hover : true }); + }, + + onMouseLeave: function() { + this.setState( { hover : false }); + }, + render: function() { var myUserId = MatrixClientPeg.get().credentials.userId; var classes = classNames({ @@ -57,14 +70,24 @@ module.exports = React.createClass({ nameCell =
    {name}
    ; } */ + + var label; + if (!this.props.collapsed) { + label =
    {name}
    ; + } + else if (this.state.hover) { + var RoomTooltip = sdk.getComponent("molecules.RoomTooltip"); + label = ; + } + var RoomAvatar = sdk.getComponent('atoms.RoomAvatar'); return ( -
    +
    { badge }
    -
    {name}
    + { label }
    ); } diff --git a/src/skins/vector/views/molecules/RoomTooltip.js b/src/skins/vector/views/molecules/RoomTooltip.js new file mode 100644 index 00000000..82e3e744 --- /dev/null +++ b/src/skins/vector/views/molecules/RoomTooltip.js @@ -0,0 +1,59 @@ +/* +Copyright 2015 OpenMarket Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +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. +*/ + +'use strict'; + +var React = require('react'); + +var dis = require('matrix-react-sdk/lib/dispatcher'); + +module.exports = React.createClass({ + displayName: 'RoomTooltip', + + componentDidMount: function() { + if (!this.props.bottom) { + // tell the roomlist about us so it can position us + dis.dispatch({ + action: 'view_tooltip', + tooltip: this.getDOMNode(), + }); + } + else { + var tooltip = this.getDOMNode(); + tooltip.style.top = tooltip.parentElement.getBoundingClientRect().top + "px"; + tooltip.style.display = "block"; + } + }, + + componentDidUnmount: function() { + if (!this.props.bottom) { + dis.dispatch({ + action: 'view_tooltip', + tooltip: null, + }); + } + }, + + render: function() { + var label = this.props.room ? this.props.room.name : this.props.label; + return ( +
    + + { label } +
    + ); + } +}); diff --git a/src/skins/vector/views/molecules/SenderProfile.js b/src/skins/vector/views/molecules/SenderProfile.js index 8be3adf2..c09685aa 100644 --- a/src/skins/vector/views/molecules/SenderProfile.js +++ b/src/skins/vector/views/molecules/SenderProfile.js @@ -19,15 +19,12 @@ limitations under the License. var React = require('react'); var classNames = require("classnames"); -var SenderProfileController = require('matrix-react-sdk/lib/controllers/molecules/SenderProfile') - // The Lato WOFF doesn't include sensible combining diacritics, so Chrome chokes on rendering them. // Revert to Arial when this happens, which on OSX works at least. var zalgo = /[\u0300-\u036f\u1ab0-\u1aff\u1dc0-\u1dff\u20d0-\u20ff\ufe20-\ufe2f]/; module.exports = React.createClass({ displayName: 'SenderProfile', - mixins: [SenderProfileController], render: function() { var mxEvent = this.props.mxEvent; diff --git a/src/skins/vector/views/molecules/UnknownMessageTile.js b/src/skins/vector/views/molecules/UnknownMessageTile.js index d5a20c87..e8cd322a 100644 --- a/src/skins/vector/views/molecules/UnknownMessageTile.js +++ b/src/skins/vector/views/molecules/UnknownMessageTile.js @@ -18,11 +18,8 @@ limitations under the License. var React = require('react'); -var UnknownMessageTileController = require('matrix-react-sdk/lib/controllers/molecules/UnknownMessageTile') - module.exports = React.createClass({ displayName: 'UnknownMessageTile', - mixins: [UnknownMessageTileController], render: function() { var content = this.props.mxEvent.getContent(); diff --git a/src/skins/vector/views/molecules/voip/IncomingCallBox.js b/src/skins/vector/views/molecules/voip/IncomingCallBox.js index ee437f0a..c3bcd825 100644 --- a/src/skins/vector/views/molecules/voip/IncomingCallBox.js +++ b/src/skins/vector/views/molecules/voip/IncomingCallBox.js @@ -31,24 +31,28 @@ module.exports = React.createClass({ }, render: function() { + + // NB: This block MUST have a "key" so React doesn't clobber the elements + // between in-call / not-in-call. + var audioBlock = ( + + ); + if (!this.state.incomingCall || !this.state.incomingCall.roomId) { return (
    - + {audioBlock}
    ); } var caller = MatrixClientPeg.get().getRoom(this.state.incomingCall.roomId).name; return (
    + {audioBlock} -
    Incoming { this.state.incomingCall ? this.state.incomingCall.type : '' } call from { caller }
    diff --git a/src/skins/vector/views/molecules/voip/VideoView.js b/src/skins/vector/views/molecules/voip/VideoView.js index 80160b78..4e0fb913 100644 --- a/src/skins/vector/views/molecules/voip/VideoView.js +++ b/src/skins/vector/views/molecules/voip/VideoView.js @@ -19,26 +19,69 @@ limitations under the License. var React = require('react'); var sdk = require('matrix-react-sdk') -var VideoViewController = require('matrix-react-sdk/lib/controllers/molecules/voip/VideoView') +var dis = require('matrix-react-sdk/lib/dispatcher') module.exports = React.createClass({ displayName: 'VideoView', - mixins: [VideoViewController], + + componentWillMount: function() { + dis.register(this.onAction); + }, getRemoteVideoElement: function() { return this.refs.remote.getDOMNode(); }, + getRemoteAudioElement: function() { + return this.refs.remoteAudio.getDOMNode(); + }, + getLocalVideoElement: function() { return this.refs.local.getDOMNode(); }, + setContainer: function(c) { + this.container = c; + }, + + onAction: function(payload) { + switch (payload.action) { + case 'video_fullscreen': + if (!this.container) { + return; + } + var element = this.container.getDOMNode(); + if (payload.fullscreen) { + var requestMethod = ( + element.requestFullScreen || + element.webkitRequestFullScreen || + element.mozRequestFullScreen || + element.msRequestFullscreen + ); + requestMethod.call(element); + } + else { + var exitMethod = ( + document.exitFullscreen || + document.mozCancelFullScreen || + document.webkitExitFullscreen || + document.msExitFullscreen + ); + if (exitMethod) { + exitMethod.call(document); + } + } + break; + } + }, + render: function() { var VideoFeed = sdk.getComponent('atoms.voip.VideoFeed'); return ( -
    +
    +
    diff --git a/src/skins/vector/views/organisms/CasLogin.js b/src/skins/vector/views/organisms/CasLogin.js new file mode 100644 index 00000000..ad9dbed9 --- /dev/null +++ b/src/skins/vector/views/organisms/CasLogin.js @@ -0,0 +1,35 @@ +/* +Copyright 2015 OpenMarket Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +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. +*/ + +'use strict'; + +var React = require('react'); + +var CasLoginController = require('matrix-react-sdk/lib/controllers/organisms/CasLogin'); + +module.exports = React.createClass({ + displayName: 'CasLogin', + mixins: [CasLoginController], + + render: function() { + return ( +
    + +
    + ); + }, + +}); diff --git a/src/skins/vector/views/organisms/LeftPanel.js b/src/skins/vector/views/organisms/LeftPanel.js index 15612704..a57f7a0a 100644 --- a/src/skins/vector/views/organisms/LeftPanel.js +++ b/src/skins/vector/views/organisms/LeftPanel.js @@ -18,21 +18,37 @@ limitations under the License. var React = require('react'); var sdk = require('matrix-react-sdk') +var dis = require('matrix-react-sdk/lib/dispatcher'); module.exports = React.createClass({ displayName: 'LeftPanel', + onHideClick: function() { + dis.dispatch({ + action: 'hide_left_panel', + }); + }, + render: function() { var RoomList = sdk.getComponent('organisms.RoomList'); var BottomLeftMenu = sdk.getComponent('molecules.BottomLeftMenu'); var IncomingCallBox = sdk.getComponent('molecules.voip.IncomingCallBox'); + var collapseButton; + var classes = "mx_LeftPanel"; + if (this.props.collapsed) { + classes += " collapsed"; + } + else { + collapseButton = < + } + return ( -