diff --git a/package.json b/package.json index eb9c3aff..96176843 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,8 @@ "q": "^1.4.1", "react": "^0.13.3", "react-loader": "^1.4.0", - "sanitize-html": "^1.11.1" + "react-dnd": "^1.1.8", + "sanitize-html": "^1.0.0" }, "devDependencies": { "babel": "^5.8.23", diff --git a/src/controllers/organisms/RoomList.js b/src/controllers/organisms/RoomList.js index 964a2648..295f5f58 100644 --- a/src/controllers/organisms/RoomList.js +++ b/src/controllers/organisms/RoomList.js @@ -23,16 +23,23 @@ var dis = require("matrix-react-sdk/lib/dispatcher"); var sdk = require('matrix-react-sdk'); var VectorConferenceHandler = require("../../modules/VectorConferenceHandler"); -var CallHandler = require("matrix-react-sdk/lib/CallHandler"); var HIDE_CONFERENCE_CHANS = true; module.exports = { + getInitialState: function() { + return { + activityMap: null, + lists: {}, + } + }, + componentWillMount: function() { var cli = MatrixClientPeg.get(); cli.on("Room", this.onRoom); cli.on("Room.timeline", this.onRoomTimeline); cli.on("Room.name", this.onRoomName); + cli.on("Room.tags", this.onRoomTags); cli.on("RoomState.events", this.onRoomStateEvents); cli.on("RoomMember.name", this.onRoomMemberName); @@ -47,11 +54,6 @@ module.exports = { onAction: function(payload) { switch (payload.action) { - // listen for call state changes to prod the render method, which - // may hide the global CallView if the call it is tracking is dead - case 'call_state': - this._recheckCallElement(this.props.selectedRoom); - break; case 'view_tooltip': this.tooltip = payload.tooltip; this._repositionTooltip(); @@ -72,7 +74,6 @@ module.exports = { componentWillReceiveProps: function(newProps) { this.state.activityMap[newProps.selectedRoom] = undefined; - this._recheckCallElement(newProps.selectedRoom); this.setState({ activityMap: this.state.activityMap }); @@ -109,6 +110,10 @@ module.exports = { this.refreshRoomList(); }, + onRoomTags: function(event, room) { + this.refreshRoomList(); + }, + onRoomStateEvents: function(ev, state) { setTimeout(this.refreshRoomList, 0); }, @@ -117,26 +122,36 @@ module.exports = { setTimeout(this.refreshRoomList, 0); }, - refreshRoomList: function() { + // TODO: rather than bluntly regenerating and re-sorting everything + // every time we see any kind of room change from the JS SDK + // we could do incremental updates on our copy of the state + // based on the room which has actually changed. This would stop + // us re-rendering all the sublists every time anything changes anywhere + // in the state of the client. this.setState(this.getRoomLists()); }, getRoomLists: function() { - var s = {}; - var inviteList = []; - s.roomList = RoomListSorter.mostRecentActivityFirst( - MatrixClientPeg.get().getRooms().filter(function(room) { - var me = room.getMember(MatrixClientPeg.get().credentials.userId); + var s = { lists: {} }; - if (me && me.membership == "invite") { - inviteList.push(room); - return false; - } + s.lists["m.invite"] = []; + s.lists["m.favourite"] = []; + s.lists["m.recent"] = []; + s.lists["m.lowpriority"] = []; + s.lists["m.archived"] = []; + MatrixClientPeg.get().getRooms().forEach(function(room) { + var me = room.getMember(MatrixClientPeg.get().credentials.userId); + + if (me && me.membership == "invite") { + s.lists["m.invite"].push(room); + } + else { var shouldShowRoom = ( me && (me.membership == "join") ); + // hiding conf rooms only ever toggles shouldShowRoom to false if (shouldShowRoom && HIDE_CONFERENCE_CHANS) { // we want to hide the 1:1 conf<->user room and not the group chat @@ -151,23 +166,28 @@ module.exports = { } } } - return shouldShowRoom; - }) - ); - s.inviteList = RoomListSorter.mostRecentActivityFirst(inviteList); - return s; - }, - _recheckCallElement: function(selectedRoomId) { - // if we aren't viewing a room with an ongoing call, but there is an - // active call, show the call element - we need to do this to make - // audio/video not crap out - var activeCall = CallHandler.getAnyActiveCall(); - var callForRoom = CallHandler.getCallForRoom(selectedRoomId); - var showCall = (activeCall && !callForRoom); - this.setState({ - show_call_element: showCall + if (shouldShowRoom) { + var tagNames = Object.keys(room.tags); + if (tagNames.length) { + for (var i = 0; i < tagNames.length; i++) { + var tagName = tagNames[i]; + s.lists[tagName] = s.lists[tagName] || []; + s.lists[tagNames[i]].push(room); + } + } + else { + s.lists["m.recent"].push(room); + } + } + } }); + + //console.log("calculated new roomLists; m.recent = " + s.lists["m.recent"]); + + // we actually apply the sorting to this when receiving the prop in RoomSubLists. + + return s; }, _repositionTooltip: function(e) { @@ -176,23 +196,4 @@ module.exports = { this.tooltip.style.top = (scroll.parentElement.offsetTop + this.tooltip.parentElement.offsetTop - scroll.scrollTop) + "px"; } }, - - makeRoomTiles: function(list, isInvite) { - var self = this; - var RoomTile = sdk.getComponent("molecules.RoomTile"); - return list.map(function(room) { - var selected = room.roomId == self.props.selectedRoom; - return ( - - ); - }); - } }; diff --git a/src/skins/vector/css/atoms/Spinner.css b/src/skins/vector/css/atoms/Spinner.css new file mode 100644 index 00000000..1c8aa97d --- /dev/null +++ b/src/skins/vector/css/atoms/Spinner.css @@ -0,0 +1,25 @@ +/* +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. +*/ + +.mx_Spinner { + display: -webkit-flex; + display: flex; + -webkit-align-items: center; + -webkit-justify-content: center; + align-items: center; + justify-content: center; + height: 100%; +} \ No newline at end of file diff --git a/src/skins/vector/css/hide.css b/src/skins/vector/css/hide.css index 7d8ee302..f84a35b3 100644 --- a/src/skins/vector/css/hide.css +++ b/src/skins/vector/css/hide.css @@ -1,4 +1,3 @@ -.mx_RoomDropTarget, .mx_RoomSettings_encrypt, .mx_CreateRoom_encrypt, .mx_RightPanel_filebutton diff --git a/src/skins/vector/css/molecules/EventTile.css b/src/skins/vector/css/molecules/EventTile.css index f9c8551b..d99bd4e1 100644 --- a/src/skins/vector/css/molecules/EventTile.css +++ b/src/skins/vector/css/molecules/EventTile.css @@ -18,13 +18,13 @@ limitations under the License. max-width: 100%; clear: both; margin-top: 24px; - margin-left: 56px; + margin-left: 65px; } .mx_EventTile_avatar { padding-left: 18px; padding-right: 12px; - margin-left: -64px; + margin-left: -73px; margin-top: -4px; float: left; } diff --git a/src/skins/vector/css/molecules/MessageComposer.css b/src/skins/vector/css/molecules/MessageComposer.css index 44e12276..2dbe05b5 100644 --- a/src/skins/vector/css/molecules/MessageComposer.css +++ b/src/skins/vector/css/molecules/MessageComposer.css @@ -32,7 +32,7 @@ limitations under the License. .mx_MessageComposer .mx_MessageComposer_avatar { display: table-cell; padding-left: 10px; - padding-right: 20px; + padding-right: 28px; height: 70px; } diff --git a/src/skins/vector/css/molecules/RoomDropTarget.css b/src/skins/vector/css/molecules/RoomDropTarget.css index c42d4499..4eea49e1 100644 --- a/src/skins/vector/css/molecules/RoomDropTarget.css +++ b/src/skins/vector/css/molecules/RoomDropTarget.css @@ -16,12 +16,46 @@ limitations under the License. .mx_RoomDropTarget { font-size: 14px; - text-align: center; - margin-left: 8px; - margin-right: 8px; - padding-top: 16px; - padding-bottom: 16px; - background-color: #fbfbfb; - border: 1px dashed #d7d7d7; - border-radius: 8px; + margin-left: 10px; + margin-right: 15px; + padding-top: 5px; + padding-bottom: 5px; + border: 1px dashed #76cfa6; + color: #454545; + background-color: rgba(255,255,255,0.5); + border-radius: 4px; +} + +.collapsed .mx_RoomDropTarget { + margin-right: 10px; +} + +.mx_RoomDropTarget_placeholder { + padding-top: 1px; + padding-bottom: 1px; +} + +.mx_RoomDropTarget_avatar { + background-color: #fff; + border-radius: 24px; + width: 24px; + height: 24px; + float: left; + margin-left: 7px; + margin-right: 7px; +} + +.mx_RoomDropTarget_label { + position: relative; + margin-top: 3px; + line-height: 21px; + z-index: 1; +} + +.collapsed .mx_RoomDropTarget_avatar { + float: none; +} + +.collapsed .mx_RoomDropTarget_label { + display: none; } diff --git a/src/skins/vector/css/molecules/RoomHeader.css b/src/skins/vector/css/molecules/RoomHeader.css index 2eeda241..e86bab2e 100644 --- a/src/skins/vector/css/molecules/RoomHeader.css +++ b/src/skins/vector/css/molecules/RoomHeader.css @@ -33,6 +33,7 @@ limitations under the License. .mx_RoomHeader_leftRow { height: 48px; margin-top: 18px; + margin-left: -2px; -webkit-box-ordinal-group: 1; -moz-box-ordinal-group: 1; @@ -103,7 +104,7 @@ limitations under the License. color: #454545; font-weight: 800; font-size: 24px; - padding-left: 8px; + padding-left: 19px; padding-right: 16px; text-overflow: ellipsis; } @@ -153,7 +154,7 @@ limitations under the License. max-height: 38px; color: #454545; font-weight: 300; - padding-left: 8px; + padding-left: 19px; padding-right: 16px; overflow: hidden; text-overflow: ellipsis; diff --git a/src/skins/vector/css/molecules/RoomTile.css b/src/skins/vector/css/molecules/RoomTile.css index f2c1daad..4bc71cb8 100644 --- a/src/skins/vector/css/molecules/RoomTile.css +++ b/src/skins/vector/css/molecules/RoomTile.css @@ -16,13 +16,13 @@ limitations under the License. .mx_RoomTile { cursor: pointer; - display: table-row; + /* This fixes wrapping of long room names, but breaks drag & drop previews */ + /* display: table-row; */ font-size: 14px; } .mx_RoomTile_avatar { display: table-cell; - background: #eaf5f0; padding-right: 8px; padding-top: 4px; padding-bottom: 2px; @@ -39,17 +39,16 @@ limitations under the License. .mx_RoomTile_name { display: table-cell; + width: 100%; vertical-align: middle; overflow: hidden; text-overflow: ellipsis; padding-right: 16px; - color: #454545; - opacity: 0.8; + color: rgba(69, 69, 69, 0.8); } .mx_RoomTile_invite { - opacity: 0.5; - font-weight: normal; + color: rgba(69, 69, 69, 0.5); } .collapsed .mx_RoomTile_name { @@ -106,15 +105,16 @@ limitations under the License. .mx_RoomTile_unread, .mx_RoomTile_highlight, -.mx_RoomTile_invited +.mx_RoomTile_selected { font-weight: bold; } -.mx_RoomTile_selected { +.mx_RoomTile_selected .mx_RoomTile_name { + color: #76cfa6 ! important; } -.mx_RoomTile.mx_RoomTile_selected { +.mx_RoomTile.mx_RoomTile_selected .mx_RoomTile_name { background: url('img/selected.png'); background-repeat: no-repeat; background-position: right center; diff --git a/src/skins/vector/css/molecules/RoomTooltip.css b/src/skins/vector/css/molecules/RoomTooltip.css index 604c6a56..4e831d48 100644 --- a/src/skins/vector/css/molecules/RoomTooltip.css +++ b/src/skins/vector/css/molecules/RoomTooltip.css @@ -21,7 +21,6 @@ limitations under the License. border-radius: 8px; background-color: #fff; z-index: 1000; - margin-top: 6px; left: 64px; padding: 6px; } diff --git a/src/skins/vector/css/organisms/LeftPanel.css b/src/skins/vector/css/organisms/LeftPanel.css index 67f00c35..738b0782 100644 --- a/src/skins/vector/css/organisms/LeftPanel.css +++ b/src/skins/vector/css/organisms/LeftPanel.css @@ -34,6 +34,10 @@ limitations under the License. cursor: pointer; } +.mx_LeftPanel_callView { + +} + .mx_LeftPanel .mx_RoomList { -webkit-box-ordinal-group: 1; -moz-box-ordinal-group: 1; @@ -53,8 +57,10 @@ limitations under the License. -webkit-order: 3; order: 3; - -webkit-flex: 0 0 126px; - flex: 0 0 126px; + -webkit-flex: 0 0 140px; + flex: 0 0 140px; + + background-color: rgba(118,207,166,0.19); } .mx_LeftPanel .mx_BottomLeftMenu .mx_RoomTile { @@ -62,7 +68,7 @@ limitations under the License. } .mx_LeftPanel .mx_BottomLeftMenu .mx_BottomLeftMenu_options { - margin-top: 12px; + margin-top: 17px; width: 100%; } diff --git a/src/skins/vector/css/organisms/RoomList.css b/src/skins/vector/css/organisms/RoomList.css index 34ebd1db..7f5e2272 100644 --- a/src/skins/vector/css/organisms/RoomList.css +++ b/src/skins/vector/css/organisms/RoomList.css @@ -16,13 +16,7 @@ limitations under the License. .mx_RoomList { padding-top: 24px; -} - -.mx_RoomList_invites, -.mx_RoomList_recents { - display: table; - table-layout: fixed; - width: 100%; + padding-bottom: 12px; } .mx_RoomList_expandButton { @@ -31,14 +25,3 @@ limitations under the License. padding-left: 12px; padding-right: 12px; } - -.mx_RoomList h2 { - text-transform: uppercase; - color: #3d3b39; - font-weight: 600; - font-size: 14px; - padding-left: 12px; - padding-right: 12px; - margin-top: 8px; - margin-bottom: 4px; -} diff --git a/src/skins/vector/css/organisms/RoomSubList.css b/src/skins/vector/css/organisms/RoomSubList.css new file mode 100644 index 00000000..57d23a38 --- /dev/null +++ b/src/skins/vector/css/organisms/RoomSubList.css @@ -0,0 +1,45 @@ +/* +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. +*/ + +.mx_RoomSubList { + display: table; + table-layout: fixed; + width: 100%; +} + +.mx_RoomSubList_bottommost { + /* XXX: this should really be 100% of the RoomList height, but can't seem to get at it */ + min-height: 400px; +} + +.mx_RoomSubList_label { + text-transform: uppercase; + color: #3d3b39; + font-weight: 600; + font-size: 14px; + padding-left: 12px; + padding-right: 12px; + margin-top: 8px; + margin-bottom: 4px; +} + +.mx_RoomSubList_chevron { + padding-left: 5px; +} + +.collapsed .mx_RoomSubList_chevron { + padding-left: 13px; +} diff --git a/src/skins/vector/css/organisms/RoomView.css b/src/skins/vector/css/organisms/RoomView.css index 508e3457..191742f5 100644 --- a/src/skins/vector/css/organisms/RoomView.css +++ b/src/skins/vector/css/organisms/RoomView.css @@ -129,7 +129,7 @@ limitations under the License. clear: both; margin-top: 32px; margin-bottom: 8px; - margin-left: 54px; + margin-left: 63px; padding-bottom: 6px; border-bottom: 1px solid #eee; } @@ -170,7 +170,7 @@ limitations under the License. .mx_RoomView_statusAreaBox_line { border-top: 1px solid #eee; - margin-left: 54px; + margin-left: 63px; height: 1px; } @@ -216,14 +216,14 @@ limitations under the License. .mx_RoomView_typingBar { margin-top: 10px; - margin-left: 54px; + margin-left: 63px; color: #4a4a4a; opacity: 0.5; } .mx_RoomView_typingImage { display: inline; - margin-left: -38px; + margin-left: -47px; margin-top: -4px; float: left; } @@ -243,7 +243,7 @@ limitations under the License. .mx_RoomView_uploadProgressOuter { height: 4px; - margin-left: 54px; + margin-left: 63px; margin-top: -1px; } @@ -254,7 +254,7 @@ limitations under the License. .mx_RoomView_uploadFilename { margin-top: 5px; - margin-left: 56px; + margin-left: 65px; opacity: 0.5; color: #4a4a4a; } diff --git a/src/skins/vector/css/pages/MatrixChat.css b/src/skins/vector/css/pages/MatrixChat.css index e74eb6c8..e6d7d30b 100644 --- a/src/skins/vector/css/pages/MatrixChat.css +++ b/src/skins/vector/css/pages/MatrixChat.css @@ -71,8 +71,8 @@ limitations under the License. background-color: #eaf5f0; - -webkit-flex: 0 0 230px; - flex: 0 0 230px; + -webkit-flex: 0 0 210px; + flex: 0 0 210px; } .mx_MatrixChat .mx_LeftPanel.collapsed { @@ -87,8 +87,8 @@ limitations under the License. -webkit-order: 2; order: 2; - padding-left: 12px; - padding-right: 12px; + padding-left: 25px; + padding-right: 22px; background-color: #fff; -webkit-flex: 1; @@ -116,8 +116,8 @@ limitations under the License. -webkit-order: 3; order: 3; - -webkit-flex: 0 0 230px; - flex: 0 0 230px; + -webkit-flex: 0 0 235px; + flex: 0 0 235px; } .mx_MatrixChat .mx_RightPanel.collapsed { diff --git a/src/skins/vector/img/list-close.png b/src/skins/vector/img/list-close.png new file mode 100644 index 00000000..82b322f9 Binary files /dev/null and b/src/skins/vector/img/list-close.png differ diff --git a/src/skins/vector/img/list-open.png b/src/skins/vector/img/list-open.png new file mode 100644 index 00000000..f8c80631 Binary files /dev/null and b/src/skins/vector/img/list-open.png differ diff --git a/src/skins/vector/skindex.js b/src/skins/vector/skindex.js index e715656c..cf279c87 100644 --- a/src/skins/vector/skindex.js +++ b/src/skins/vector/skindex.js @@ -30,6 +30,7 @@ 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.Spinner'] = require('./views/atoms/Spinner'); 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'); @@ -80,9 +81,11 @@ skin['organisms.QuestionDialog'] = require('./views/organisms/QuestionDialog'); skin['organisms.RightPanel'] = require('./views/organisms/RightPanel'); skin['organisms.RoomDirectory'] = require('./views/organisms/RoomDirectory'); skin['organisms.RoomList'] = require('./views/organisms/RoomList'); +skin['organisms.RoomSubList'] = require('./views/organisms/RoomSubList'); skin['organisms.RoomView'] = require('./views/organisms/RoomView'); skin['organisms.UserSettings'] = require('./views/organisms/UserSettings'); skin['organisms.ViewSource'] = require('./views/organisms/ViewSource'); +skin['pages.CompatibilityPage'] = require('./views/pages/CompatibilityPage'); 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/Spinner.js b/src/skins/vector/views/atoms/Spinner.js index 908f2678..6dfd0c41 100644 --- a/src/skins/vector/views/atoms/Spinner.js +++ b/src/skins/vector/views/atoms/Spinner.js @@ -26,7 +26,7 @@ module.exports = React.createClass({ var h = this.props.h || 32; var imgClass = this.props.imgClassName || ""; return ( -
+
); diff --git a/src/skins/vector/views/molecules/RoomDropTarget.js b/src/skins/vector/views/molecules/RoomDropTarget.js index b1e15077..00d0546c 100644 --- a/src/skins/vector/views/molecules/RoomDropTarget.js +++ b/src/skins/vector/views/molecules/RoomDropTarget.js @@ -18,16 +18,25 @@ limitations under the License. var React = require('react'); -//var RoomDropTargetController = require('matrix-react-sdk/lib/controllers/molecules/RoomDropTargetController') - module.exports = React.createClass({ displayName: 'RoomDropTarget', - // mixins: [RoomDropTargetController], + render: function() { - return ( -
- {this.props.text} -
- ); + if (this.props.placeholder) { + return ( +
+
+ ); + } + else { + return ( +
+
+
+ { this.props.label } +
+
+ ); + } } }); diff --git a/src/skins/vector/views/molecules/RoomTile.js b/src/skins/vector/views/molecules/RoomTile.js index ece48043..471bd8a1 100644 --- a/src/skins/vector/views/molecules/RoomTile.js +++ b/src/skins/vector/views/molecules/RoomTile.js @@ -17,6 +17,8 @@ limitations under the License. 'use strict'; var React = require('react'); +var DragSource = require('react-dnd').DragSource; +var DropTarget = require('react-dnd').DropTarget; var classNames = require('classnames'); var RoomTileController = require('matrix-react-sdk/lib/controllers/molecules/RoomTile') @@ -25,10 +27,178 @@ var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg'); var sdk = require('matrix-react-sdk') -module.exports = React.createClass({ +/** + * Specifies the drag source contract. + * Only `beginDrag` function is required. + */ +var roomTileSource = { + canDrag: function(props, monitor) { + return props.roomSubList.props.editable; + }, + + beginDrag: function (props) { + // Return the data describing the dragged item + var item = { + room: props.room, + originalList: props.roomSubList, + originalIndex: props.roomSubList.findRoomTile(props.room).index, + targetList: props.roomSubList, // at first target is same as original + lastTargetRoom: null, + lastYOffset: null, + lastYDelta: null, + }; + + if (props.roomSubList.debug) console.log("roomTile beginDrag for " + item.room.roomId); + + // doing this 'correctly' with state causes react-dnd to break seemingly due to the state transitions + props.room._dragging = true; + + return item; + }, + + endDrag: function (props, monitor, component) { + var item = monitor.getItem(); + + if (props.roomSubList.debug) console.log("roomTile endDrag for " + item.room.roomId + " with didDrop=" + monitor.didDrop()); + + props.room._dragging = false; + if (monitor.didDrop()) { + if (props.roomSubList.debug) console.log("force updating component " + item.targetList.props.label); + item.targetList.forceUpdate(); // as we're not using state + } + + if (monitor.didDrop() && item.targetList.props.editable) { + // if we moved lists, remove the old tag + if (item.targetList !== item.originalList) { + // commented out attempts to set a spinner on our target component as component is actually + // the original source component being dragged, not our target. To fix we just need to + // move all of this to endDrop in the target instead. FIXME later. + + //component.state.set({ spinner: component.state.spinner ? component.state.spinner++ : 1 }); + MatrixClientPeg.get().deleteRoomTag(item.room.roomId, item.originalList.props.tagName).finally(function() { + //component.state.set({ spinner: component.state.spinner-- }); + }).fail(function(err) { + var ErrorDialog = sdk.getComponent("organisms.ErrorDialog"); + Modal.createDialog(ErrorDialog, { + title: "Failed to remove tag " + item.originalList.props.tagName + " from room", + description: err.toString() + }); + }); + } + + var newOrder= {}; + if (item.targetList.props.order === 'manual') { + newOrder['order'] = item.targetList.calcManualOrderTagData(item.room); + } + + // if we moved lists or the ordering changed, add the new tag + if (item.targetList.props.tagName && (item.targetList !== item.originalList || newOrder)) { + //component.state.set({ spinner: component.state.spinner ? component.state.spinner++ : 1 }); + MatrixClientPeg.get().setRoomTag(item.room.roomId, item.targetList.props.tagName, newOrder).finally(function() { + //component.state.set({ spinner: component.state.spinner-- }); + }).fail(function(err) { + var ErrorDialog = sdk.getComponent("organisms.ErrorDialog"); + Modal.createDialog(ErrorDialog, { + title: "Failed to add tag " + item.targetList.props.tagName + " to room", + description: err.toString() + }); + }); + } + } + else { + // cancel the drop and reset our original position + if (props.roomSubList.debug) console.log("cancelling drop & drag"); + props.roomSubList.moveRoomTile(item.room, item.originalIndex); + if (item.targetList && item.targetList !== item.originalList) { + item.targetList.removeRoomTile(item.room); + } + } + } +}; + +var roomTileTarget = { + canDrop: function() { + return false; + }, + + hover: function(props, monitor) { + var item = monitor.getItem(); + var off = monitor.getClientOffset(); + // console.log("hovering on room " + props.room.roomId + ", isOver=" + monitor.isOver()); + + //console.log("item.targetList=" + item.targetList + ", roomSubList=" + props.roomSubList); + + var switchedTarget = false; + if (item.targetList !== props.roomSubList) { + // we've switched target, so remove the tile from the previous target. + // n.b. the previous target might actually be the source list. + if (props.roomSubList.debug) console.log("switched target sublist"); + switchedTarget = true; + item.targetList.removeRoomTile(item.room); + item.targetList = props.roomSubList; + } + + if (!item.targetList.props.editable) return; + + if (item.targetList.props.order === 'manual') { + if (item.room.roomId !== props.room.roomId && props.room !== item.lastTargetRoom) { + // find the offset of the target tile in the list. + var roomTile = props.roomSubList.findRoomTile(props.room); + // shuffle the list to add our tile to that position. + props.roomSubList.moveRoomTile(item.room, roomTile.index); + } + + // stop us from flickering between our droptarget and the previous room. + // whenever the cursor changes direction we have to reset the flicker-damping. + + var yDelta = off.y - item.lastYOffset; + + if ((yDelta > 0 && item.lastYDelta < 0) || + (yDelta < 0 && item.lastYDelta > 0)) + { + // the cursor changed direction - forget our previous room + item.lastTargetRoom = null; + } + else { + // track the last room we were hovering over so we can stop + // bouncing back and forth if the droptarget is narrower than + // the other list items. The other way to do this would be + // to reduce the size of the hittarget on the list items, but + // can't see an easy way to do that. + item.lastTargetRoom = props.room; + } + + if (yDelta) item.lastYDelta = yDelta; + item.lastYOffset = off.y; + } + else if (switchedTarget) { + if (!props.roomSubList.findRoomTile(item.room).room) { + // add to the list in the right place + props.roomSubList.moveRoomTile(item.room, 0); + } + // we have to sort the list whatever to recalculate it + props.roomSubList.sortList(); + } + }, +}; + +var RoomTile = React.createClass({ displayName: 'RoomTile', mixins: [RoomTileController], + propTypes: { + connectDragSource: React.PropTypes.func.isRequired, + connectDropTarget: React.PropTypes.func.isRequired, + isDragging: React.PropTypes.bool.isRequired, + room: React.PropTypes.object.isRequired, + collapsed: React.PropTypes.bool.isRequired, + selected: React.PropTypes.bool.isRequired, + unread: React.PropTypes.bool.isRequired, + highlight: React.PropTypes.bool.isRequired, + isInvite: React.PropTypes.bool.isRequired, + roomSubList: React.PropTypes.object.isRequired, + }, + getInitialState: function() { return( { hover : false }); }, @@ -42,18 +212,28 @@ module.exports = React.createClass({ }, render: function() { + // if (this.props.clientOffset) { + // //console.log("room " + this.props.room.roomId + " has dropTarget clientOffset " + this.props.clientOffset.x + "," + this.props.clientOffset.y); + // } + + if (this.props.room._dragging) { + var RoomDropTarget = sdk.getComponent("molecules.RoomDropTarget"); + return ; + } + var myUserId = MatrixClientPeg.get().credentials.userId; + var me = this.props.room.currentState.members[myUserId]; var classes = classNames({ 'mx_RoomTile': true, 'mx_RoomTile_selected': this.props.selected, 'mx_RoomTile_unread': this.props.unread, 'mx_RoomTile_highlight': this.props.highlight, - 'mx_RoomTile_invited': this.props.room.currentState.members[myUserId].membership == 'invite' + 'mx_RoomTile_invited': (me && me.membership == 'invite'), }); var name; if (this.props.isInvite) { - name = this.props.room.getMember(MatrixClientPeg.get().credentials.userId).events.member.getSender(); + name = this.props.room.getMember(myUserId).events.member.getSender(); } else { // XXX: We should never display raw room IDs, but sometimes the room name js sdk gives is undefined @@ -92,7 +272,14 @@ module.exports = React.createClass({ } var RoomAvatar = sdk.getComponent('atoms.RoomAvatar'); - return ( + + // These props are injected by React DnD, + // as defined by your `collect` function above: + var isDragging = this.props.isDragging; + var connectDragSource = this.props.connectDragSource; + var connectDropTarget = this.props.connectDropTarget; + + return connectDragSource(connectDropTarget(
@@ -100,6 +287,27 @@ module.exports = React.createClass({
{ label }
- ); + )); } }); + +// Export the wrapped version, inlining the 'collect' functions +// to more closely resemble the ES7 +module.exports = +DropTarget('RoomTile', roomTileTarget, function(connect, monitor) { + return { + // Call this function inside render() + // to let React DnD handle the drag events: + connectDropTarget: connect.dropTarget(), + isOver: monitor.isOver(), + } +})( +DragSource('RoomTile', roomTileSource, function(connect, monitor) { + return { + // Call this function inside render() + // to let React DnD handle the drag events: + connectDragSource: connect.dragSource(), + // You can ask the monitor about the current drag state: + isDragging: monitor.isDragging() + }; +})(RoomTile)); \ No newline at end of file diff --git a/src/skins/vector/views/molecules/voip/CallView.js b/src/skins/vector/views/molecules/voip/CallView.js index 07987bd3..52297bbc 100644 --- a/src/skins/vector/views/molecules/voip/CallView.js +++ b/src/skins/vector/views/molecules/voip/CallView.js @@ -34,7 +34,7 @@ module.exports = React.createClass({ render: function(){ var VideoView = sdk.getComponent('molecules.voip.VideoView'); return ( - + ); } }); diff --git a/src/skins/vector/views/molecules/voip/VideoView.js b/src/skins/vector/views/molecules/voip/VideoView.js index 4e0fb913..e19f5704 100644 --- a/src/skins/vector/views/molecules/voip/VideoView.js +++ b/src/skins/vector/views/molecules/voip/VideoView.js @@ -78,7 +78,7 @@ module.exports = React.createClass({ render: function() { var VideoFeed = sdk.getComponent('atoms.voip.VideoFeed'); return ( -
+