2014-08-13 04:32:18 +02:00
/ *
2014-09-03 18:29:13 +02:00
Copyright 2014 OpenMarket Ltd
2014-08-13 04:32:18 +02:00
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 .
* /
2014-11-11 05:39:16 +01:00
angular . module ( 'RoomController' , [ 'ngSanitize' , 'matrixFilter' , 'mFileInput' , 'angular-peity' ] )
2014-11-13 16:21:42 +01:00
. controller ( 'RoomController' , [ '$modal' , '$filter' , '$scope' , '$timeout' , '$routeParams' , '$location' , '$rootScope' , 'matrixService' , 'mPresence' , 'eventHandlerService' , 'mFileUpload' , 'matrixPhoneService' , 'MatrixCall' , 'modelService' , 'recentsService' , 'commandsService' ,
function ( $modal , $filter , $scope , $timeout , $routeParams , $location , $rootScope , matrixService , mPresence , eventHandlerService , mFileUpload , matrixPhoneService , MatrixCall , modelService , recentsService , commandsService ) {
2014-08-12 16:10:52 +02:00
'use strict' ;
2014-08-16 01:14:47 +02:00
var MESSAGES _PER _PAGINATION = 30 ;
2014-08-21 14:30:41 +02:00
var THUMBNAIL _SIZE = 320 ;
2014-10-31 12:20:07 +01:00
// .html needs this
2014-11-12 18:06:03 +01:00
$scope . containsBingWord = eventHandlerService . eventContainsBingWord ;
2014-08-18 17:11:08 +02:00
2014-08-18 17:40:05 +02:00
// Room ids. Computed and resolved in onInit
$scope . room _id = undefined ;
2014-08-18 17:11:08 +02:00
$scope . room _alias = undefined ;
2014-08-12 16:10:52 +02:00
$scope . state = {
user _id : matrixService . config ( ) . user _id ,
2014-09-11 16:54:51 +02:00
permission _denied : undefined , // If defined, this string contains the reason why the user cannot join the room
2014-08-21 16:27:15 +02:00
first _pagination : true , // this is toggled off when the first pagination is done
2014-09-09 16:46:30 +02:00
can _paginate : false , // this is toggled off when we are not ready yet to paginate or when we run out of items
2014-08-16 01:14:47 +02:00
paginating : false , // used to avoid concurrent pagination requests pulling in dup contents
2014-09-09 14:18:08 +02:00
stream _failure : undefined , // the response when the stream fails
2014-09-17 17:12:52 +02:00
waiting _for _joined _event : false , // true when the join request is pending. Back to false once the corresponding m.room.member event is received
2014-09-19 17:20:27 +02:00
messages _visibility : "hidden" , // In order to avoid flickering when scrolling down the message table at the page opening, delay the message table display
2014-08-12 16:10:52 +02:00
} ;
$scope . members = { } ;
2014-08-13 11:42:28 +02:00
$scope . imageURLToSend = "" ;
2014-08-14 18:23:47 +02:00
2014-09-12 10:48:06 +02:00
// vars and functions for updating the name
$scope . name = {
isEditing : false ,
newNameText : "" ,
editName : function ( ) {
if ( $scope . name . isEditing ) {
console . log ( "Warning: Already editing name." ) ;
return ;
} ;
2014-10-31 18:13:27 +01:00
var nameEvent = $scope . room . current _room _state . state _events [ 'm.room.name' ] ;
2014-09-17 15:46:12 +02:00
if ( nameEvent ) {
$scope . name . newNameText = nameEvent . content . name ;
}
else {
$scope . name . newNameText = "" ;
}
2014-09-12 10:48:06 +02:00
// Force focus to the input
$timeout ( function ( ) {
angular . element ( '.roomNameInput' ) . focus ( ) ;
} , 0 ) ;
$scope . name . isEditing = true ;
} ,
updateName : function ( ) {
console . log ( "Updating name to " + $scope . name . newNameText ) ;
matrixService . setName ( $scope . room _id , $scope . name . newNameText ) . then (
function ( ) {
} ,
function ( error ) {
$scope . feedback = "Request failed: " + error . data . error ;
}
) ;
$scope . name . isEditing = false ;
} ,
cancelEdit : function ( ) {
$scope . name . isEditing = false ;
}
} ;
2014-09-09 03:40:34 +02:00
// vars and functions for updating the topic
$scope . topic = {
isEditing : false ,
newTopicText : "" ,
editTopic : function ( ) {
if ( $scope . topic . isEditing ) {
console . log ( "Warning: Already editing topic." ) ;
return ;
}
2014-10-31 18:13:27 +01:00
var topicEvent = $scope . room . current _room _state . state _events [ 'm.room.topic' ] ;
2014-09-09 03:59:26 +02:00
if ( topicEvent ) {
$scope . topic . newTopicText = topicEvent . content . topic ;
}
else {
$scope . topic . newTopicText = "" ;
}
2014-09-11 11:49:59 +02:00
// Force focus to the input
$timeout ( function ( ) {
angular . element ( '.roomTopicInput' ) . focus ( ) ;
} , 0 ) ;
2014-09-09 03:40:34 +02:00
$scope . topic . isEditing = true ;
} ,
updateTopic : function ( ) {
console . log ( "Updating topic to " + $scope . topic . newTopicText ) ;
2014-09-11 11:53:37 +02:00
matrixService . setTopic ( $scope . room _id , $scope . topic . newTopicText ) . then (
function ( ) {
} ,
function ( error ) {
$scope . feedback = "Request failed: " + error . data . error ;
}
) ;
2014-09-09 03:40:34 +02:00
$scope . topic . isEditing = false ;
} ,
cancelEdit : function ( ) {
$scope . topic . isEditing = false ;
}
2014-09-12 10:48:06 +02:00
} ;
2014-09-09 03:40:34 +02:00
2014-09-05 17:52:11 +02:00
var scrollToBottom = function ( force ) {
2014-08-16 23:05:31 +02:00
console . log ( "Scrolling to bottom" ) ;
2014-09-05 17:52:11 +02:00
// Do not autoscroll to the bottom to display the new event if the user is not at the bottom.
// Exception: in case where the event is from the user, we want to force scroll to the bottom
var objDiv = document . getElementById ( "messageTableWrapper" ) ;
2014-10-29 12:03:58 +01:00
// add a 10px buffer to this check so if the message list is not *quite*
// at the bottom it still scrolls since it basically is at the bottom.
if ( ( 10 + objDiv . offsetHeight + objDiv . scrollTop >= objDiv . scrollHeight ) || force ) {
2014-09-05 17:52:11 +02:00
$timeout ( function ( ) {
objDiv . scrollTop = objDiv . scrollHeight ;
2014-09-17 17:12:52 +02:00
// Show the message table once the first scrolldown is done
if ( "visible" !== $scope . state . messages _visibility ) {
$timeout ( function ( ) {
$scope . state . messages _visibility = "visible" ;
} , 0 ) ;
}
2014-09-05 17:52:11 +02:00
} , 0 ) ;
}
2014-08-14 18:23:47 +02:00
} ;
2014-08-29 15:00:20 +02:00
2014-08-15 12:31:13 +02:00
$scope . $on ( eventHandlerService . MSG _EVENT , function ( ngEvent , event , isLive ) {
2014-08-15 13:51:20 +02:00
if ( isLive && event . room _id === $scope . room _id ) {
2014-09-05 17:52:11 +02:00
scrollToBottom ( ) ;
2014-08-14 18:23:47 +02:00
}
2014-08-15 12:31:13 +02:00
} ) ;
$scope . $on ( eventHandlerService . MEMBER _EVENT , function ( ngEvent , event , isLive ) {
2014-09-17 09:41:21 +02:00
if ( isLive && event . room _id === $scope . room _id ) {
2014-09-09 14:18:08 +02:00
if ( $scope . state . waiting _for _joined _event ) {
// The user has successfully joined the room, we can getting data for this room
$scope . state . waiting _for _joined _event = false ;
onInit3 ( ) ;
}
2014-09-11 16:54:51 +02:00
else if ( event . state _key === $scope . state . user _id && "invite" !== event . membership && "join" !== event . membership ) {
var user ;
if ( $scope . members [ event . user _id ] ) {
user = $scope . members [ event . user _id ] . displayname ;
}
if ( user ) {
user = user + " (" + event . user _id + ")" ;
}
else {
user = event . user _id ;
}
if ( "ban" === event . membership ) {
$scope . state . permission _denied = "You have been banned by " + user ;
}
else {
$scope . state . permission _denied = "You have been kicked by " + user ;
2014-09-17 09:41:21 +02:00
}
2014-09-11 16:54:51 +02:00
}
2014-09-09 14:18:08 +02:00
else {
scrollToBottom ( ) ;
updateMemberList ( event ) ;
}
2014-09-02 09:39:43 +02:00
}
2014-08-15 12:31:13 +02:00
} ) ;
$scope . $on ( eventHandlerService . PRESENCE _EVENT , function ( ngEvent , event , isLive ) {
2014-09-02 09:39:43 +02:00
if ( isLive ) {
updatePresence ( event ) ;
}
2014-08-15 12:31:13 +02:00
} ) ;
2014-09-03 14:12:56 +02:00
$scope . $on ( eventHandlerService . POWERLEVEL _EVENT , function ( ngEvent , event , isLive ) {
if ( isLive && event . room _id === $scope . room _id ) {
for ( var user _id in event . content ) {
updateUserPowerLevel ( user _id ) ;
}
}
} ) ;
2014-08-27 19:57:54 +02:00
2014-08-29 15:00:20 +02:00
$scope . memberCount = function ( ) {
return Object . keys ( $scope . members ) . length ;
} ;
2014-08-14 18:23:47 +02:00
2014-08-15 18:42:02 +02:00
$scope . paginateMore = function ( ) {
if ( $scope . state . can _paginate ) {
2014-08-16 01:14:47 +02:00
// console.log("Paginating more.");
2014-08-16 23:05:31 +02:00
paginate ( MESSAGES _PER _PAGINATION ) ;
2014-08-15 18:42:02 +02:00
}
} ;
2014-08-28 20:03:34 +02:00
2014-08-16 23:05:31 +02:00
var paginate = function ( numItems ) {
2014-09-13 12:35:36 +02:00
//console.log("paginate " + numItems + " and first_pagination is " + $scope.state.first_pagination);
2014-08-20 17:08:05 +02:00
if ( $scope . state . paginating || ! $scope . room _id ) {
2014-08-16 01:14:47 +02:00
return ;
}
else {
$scope . state . paginating = true ;
}
2014-09-10 12:01:00 +02:00
2014-10-31 17:22:15 +01:00
console . log ( "paginateBackMessages from " + $scope . room . old _room _state . pagination _token + " for " + numItems ) ;
2014-08-16 01:14:47 +02:00
var originalTopRow = $ ( "#messageTable>tbody>tr:first" ) [ 0 ] ;
2014-09-10 12:01:00 +02:00
// Paginate events from the point in cache
2014-10-31 17:22:15 +01:00
matrixService . paginateBackMessages ( $scope . room _id , $scope . room . old _room _state . pagination _token , numItems ) . then (
2014-08-14 18:23:47 +02:00
function ( response ) {
2014-09-10 12:01:00 +02:00
2014-09-16 15:03:07 +02:00
eventHandlerService . handleRoomMessages ( $scope . room _id , response . data , false , 'b' ) ;
2014-08-14 18:23:47 +02:00
if ( response . data . chunk . length < MESSAGES _PER _PAGINATION ) {
2014-08-16 01:14:47 +02:00
// no more messages to paginate. this currently never gets turned true again, as we never
// expire paginated contents in the current implementation.
2014-08-14 18:23:47 +02:00
$scope . state . can _paginate = false ;
}
2014-08-15 18:42:02 +02:00
2014-08-16 23:05:31 +02:00
$scope . state . paginating = false ;
var wrapper = $ ( "#messageTableWrapper" ) [ 0 ] ;
var table = $ ( "#messageTable" ) [ 0 ] ;
// console.log("wrapper height=" + wrapper.clientHeight + ", table scrollHeight=" + table.scrollHeight);
if ( $scope . state . can _paginate ) {
// check we don't have to pull in more messages
// n.b. we dispatch through a timeout() to allow the digest to run otherwise the .height methods are stale
$timeout ( function ( ) {
if ( table . scrollHeight < wrapper . clientHeight ) {
paginate ( MESSAGES _PER _PAGINATION ) ;
scrollToBottom ( ) ;
}
} , 0 ) ;
}
2014-08-21 16:27:15 +02:00
if ( $scope . state . first _pagination ) {
2014-09-13 12:35:36 +02:00
scrollToBottom ( true ) ;
2014-08-21 16:27:15 +02:00
$scope . state . first _pagination = false ;
2014-08-15 18:42:02 +02:00
}
2014-08-16 01:14:47 +02:00
else {
2014-08-16 23:05:31 +02:00
// lock the scroll position
2014-08-16 01:14:47 +02:00
$timeout ( function ( ) {
// FIXME: this risks a flicker before the scrollTop is actually updated, but we have to
// dispatch it into a function in order to first update the layout. The right solution
// might be to implement it as a directive, more like
// http://stackoverflow.com/questions/23736647/how-to-retain-scroll-position-of-ng-repeat-in-angularjs
// however, this specific solution breaks because it measures the rows height before
// the contents are interpolated.
2014-08-16 23:05:31 +02:00
wrapper . scrollTop = originalTopRow ? ( originalTopRow . offsetTop + wrapper . scrollTop ) : 0 ;
2014-08-16 01:14:47 +02:00
} , 0 ) ;
}
2014-08-14 18:23:47 +02:00
} ,
function ( error ) {
2014-08-14 18:40:27 +02:00
console . log ( "Failed to paginateBackMessages: " + JSON . stringify ( error ) ) ;
2014-08-16 01:14:47 +02:00
$scope . state . paginating = false ;
2014-08-14 18:23:47 +02:00
}
2014-08-20 17:08:05 +02:00
) ;
2014-08-14 18:23:47 +02:00
} ;
2014-08-12 16:10:52 +02:00
var updateMemberList = function ( chunk ) {
2014-08-22 11:56:09 +02:00
if ( chunk . room _id != $scope . room _id ) return ;
2014-09-03 11:38:24 +02:00
2014-08-26 11:24:47 +02:00
// set target_user_id to keep things clear
var target _user _id = chunk . state _key ;
var isNewMember = ! ( target _user _id in $scope . members ) ;
2014-08-12 16:10:52 +02:00
if ( isNewMember ) {
2014-09-05 18:05:23 +02:00
// Ignore banned and kicked (leave) people
if ( "ban" === chunk . membership || "leave" === chunk . membership ) {
return ;
}
2014-08-16 14:22:18 +02:00
// FIXME: why are we copying these fields around inside chunk?
2014-09-01 19:09:17 +02:00
if ( "presence" in chunk . content ) {
chunk . presence = chunk . content . presence ;
2014-08-15 18:47:45 +02:00
}
2014-09-01 19:09:17 +02:00
if ( "last_active_ago" in chunk . content ) {
chunk . last _active _ago = chunk . content . last _active _ago ;
2014-09-02 16:39:17 +02:00
$scope . now = new Date ( ) . getTime ( ) ;
chunk . last _updated = $scope . now ;
2014-08-16 02:07:23 +02:00
}
2014-08-16 14:22:18 +02:00
if ( "displayname" in chunk . content ) {
chunk . displayname = chunk . content . displayname ;
}
if ( "avatar_url" in chunk . content ) {
chunk . avatar _url = chunk . content . avatar _url ;
}
2014-08-29 18:21:57 +02:00
$scope . members [ target _user _id ] = chunk ;
2014-08-22 11:50:38 +02:00
2014-11-13 17:43:53 +01:00
var usr = modelService . getUser ( target _user _id ) ;
if ( usr ) {
updatePresence ( usr . event ) ;
2014-08-22 11:50:38 +02:00
}
2014-08-12 16:10:52 +02:00
}
else {
2014-09-01 16:21:13 +02:00
// selectively update membership and presence else it will nuke the picture and displayname too :/
2014-09-05 18:05:23 +02:00
// Remove banned and kicked (leave) people
if ( "ban" === chunk . membership || "leave" === chunk . membership ) {
delete $scope . members [ target _user _id ] ;
return ;
}
2014-08-26 11:24:47 +02:00
var member = $scope . members [ target _user _id ] ;
2014-09-01 16:21:13 +02:00
member . membership = chunk . content . membership ;
2014-09-01 19:09:17 +02:00
if ( "presence" in chunk . content ) {
member . presence = chunk . content . presence ;
2014-09-01 16:21:13 +02:00
}
2014-09-01 19:09:17 +02:00
if ( "last_active_ago" in chunk . content ) {
member . last _active _ago = chunk . content . last _active _ago ;
2014-09-02 16:39:17 +02:00
$scope . now = new Date ( ) . getTime ( ) ;
member . last _updated = $scope . now ;
2014-09-01 16:21:13 +02:00
}
2014-08-12 16:10:52 +02:00
}
2014-08-29 13:30:20 +02:00
} ;
2014-08-29 18:21:57 +02:00
var updateMemberListPresenceAge = function ( ) {
$scope . now = new Date ( ) . getTime ( ) ;
2014-08-29 18:54:11 +02:00
// TODO: don't bother polling every 5s if we know none of our counters are younger than 1 minute
2014-08-29 18:21:57 +02:00
$timeout ( updateMemberListPresenceAge , 5 * 1000 ) ;
} ;
2014-08-12 16:10:52 +02:00
var updatePresence = function ( chunk ) {
if ( ! ( chunk . content . user _id in $scope . members ) ) {
console . log ( "updatePresence: Unknown member for chunk " + JSON . stringify ( chunk ) ) ;
return ;
}
var member = $scope . members [ chunk . content . user _id ] ;
2014-08-16 02:07:23 +02:00
// XXX: why not just pass the chunk straight through?
2014-09-01 19:09:17 +02:00
if ( "presence" in chunk . content ) {
member . presence = chunk . content . presence ;
2014-08-16 02:07:23 +02:00
}
2014-09-01 19:09:17 +02:00
if ( "last_active_ago" in chunk . content ) {
member . last _active _ago = chunk . content . last _active _ago ;
2014-09-02 16:39:17 +02:00
$scope . now = new Date ( ) . getTime ( ) ;
member . last _updated = $scope . now ;
2014-08-12 16:10:52 +02:00
}
// this may also contain a new display name or avatar url, so check.
if ( "displayname" in chunk . content ) {
member . displayname = chunk . content . displayname ;
}
if ( "avatar_url" in chunk . content ) {
member . avatar _url = chunk . content . avatar _url ;
}
2014-08-29 13:30:20 +02:00
} ;
2014-08-12 16:10:52 +02:00
2014-09-02 11:54:11 +02:00
var updateUserPowerLevel = function ( user _id ) {
var member = $scope . members [ user _id ] ;
if ( member ) {
2014-11-03 11:23:14 +01:00
member . powerLevel = eventHandlerService . getUserPowerLevel ( $scope . room _id , user _id ) ;
2014-09-03 18:55:27 +02:00
normaliseMembersPowerLevels ( ) ;
}
2014-09-05 11:13:33 +02:00
} ;
2014-09-03 18:55:27 +02:00
// Normalise users power levels so that the user with the higher power level
// will have a bar covering 100% of the width of his avatar
var normaliseMembersPowerLevels = function ( ) {
// Find the max power level
var maxPowerLevel = 0 ;
for ( var i in $scope . members ) {
2014-09-24 12:22:40 +02:00
if ( ! $scope . members . hasOwnProperty ( i ) ) continue ;
2014-09-03 18:55:27 +02:00
var member = $scope . members [ i ] ;
if ( member . powerLevel ) {
maxPowerLevel = Math . max ( maxPowerLevel , member . powerLevel ) ;
}
}
// Normalized them on a 0..100% scale to be use in css width
if ( maxPowerLevel ) {
for ( var i in $scope . members ) {
2014-09-24 12:22:40 +02:00
if ( ! $scope . members . hasOwnProperty ( i ) ) continue ;
2014-09-03 18:55:27 +02:00
var member = $scope . members [ i ] ;
member . powerLevelNorm = ( member . powerLevel * 100 ) / maxPowerLevel ;
}
2014-09-02 11:54:11 +02:00
}
2014-09-05 11:13:33 +02:00
} ;
2014-09-02 11:54:11 +02:00
2014-08-12 16:10:52 +02:00
$scope . send = function ( ) {
2014-09-20 01:49:45 +02:00
var input = $ ( '#mainInput' ) . val ( ) ;
if ( undefined === input || input === "" ) {
2014-08-12 16:10:52 +02:00
return ;
}
2014-08-22 02:54:37 +02:00
2014-09-05 17:52:11 +02:00
scrollToBottom ( true ) ;
2014-09-17 14:18:39 +02:00
// Store the command in the history
2014-11-13 17:09:44 +01:00
$rootScope . $broadcast ( "commandHistory:BROADCAST_NEW_HISTORY_ITEM(item)" ,
input ) ;
2014-09-17 14:18:39 +02:00
2014-11-13 12:55:02 +01:00
var isEmote = input . indexOf ( "/me " ) === 0 ;
2014-11-13 12:58:28 +01:00
var promise ;
if ( ! isEmote ) {
promise = commandsService . processInput ( $scope . room _id , input ) ;
}
var echo = false ;
2014-09-03 11:07:44 +02:00
2014-11-13 12:55:02 +01:00
if ( ! promise ) { // not a non-echoable command
2014-09-06 19:13:38 +02:00
echo = true ;
2014-11-13 12:55:02 +01:00
if ( isEmote ) {
promise = matrixService . sendEmoteMessage ( $scope . room _id , input . substring ( 4 ) ) ;
}
else {
promise = matrixService . sendTextMessage ( $scope . room _id , input ) ;
}
2014-09-06 19:13:38 +02:00
}
if ( echo ) {
2014-09-05 14:09:14 +02:00
// Echo the message to the room
// To do so, create a minimalist fake text message event and add it to the in-memory list of room messages
var echoMessage = {
content : {
2014-11-13 12:55:02 +01:00
body : ( isEmote ? input . substring ( 4 ) : input ) ,
msgtype : ( isEmote ? "m.emote" : "m.text" ) ,
2014-09-05 14:09:14 +02:00
} ,
2014-10-18 00:11:55 +02:00
origin _server _ts : new Date ( ) . getTime ( ) , // fake a timestamp
2014-09-05 14:09:14 +02:00
room _id : $scope . room _id ,
type : "m.room.message" ,
user _id : $scope . state . user _id ,
2014-09-10 18:24:03 +02:00
echo _msg _state : "messagePending" // Add custom field to indicate the state of this fake message to HTML
2014-09-05 14:09:14 +02:00
} ;
2014-09-20 01:49:45 +02:00
$ ( '#mainInput' ) . val ( '' ) ;
2014-10-31 18:13:27 +01:00
$scope . room . addMessageEvent ( echoMessage ) ;
2014-09-05 14:09:14 +02:00
scrollToBottom ( ) ;
2014-08-12 16:10:52 +02:00
}
2014-09-04 18:40:15 +02:00
if ( promise ) {
2014-09-10 17:40:34 +02:00
// Reset previous feedback
$scope . feedback = "" ;
2014-09-04 18:40:15 +02:00
promise . then (
2014-09-10 18:24:03 +02:00
function ( response ) {
2014-09-04 18:40:15 +02:00
console . log ( "Request successfully sent" ) ;
2014-09-12 19:19:32 +02:00
2014-09-10 18:24:03 +02:00
if ( echo ) {
// Mark this fake message event with its allocated event_id
// When the true message event will come from the events stream (in handleMessage),
// we will be able to replace the fake one by the true one
echoMessage . event _id = response . data . event _id ;
2014-09-05 14:09:14 +02:00
}
else {
2014-09-20 01:49:45 +02:00
$ ( '#mainInput' ) . val ( '' ) ;
2014-09-10 18:24:03 +02:00
}
2014-09-04 18:40:15 +02:00
} ,
function ( error ) {
2014-11-13 12:55:02 +01:00
$scope . feedback = error . data . error ;
2014-09-05 14:09:14 +02:00
if ( echoMessage ) {
// Mark the message as unsent for the rest of the page life
2014-10-18 00:11:55 +02:00
echoMessage . origin _server _ts = "Unsent" ;
2014-09-05 14:09:14 +02:00
echoMessage . echo _msg _state = "messageUnSent" ;
}
2014-09-04 18:40:15 +02:00
} ) ;
}
2014-08-12 16:10:52 +02:00
} ;
$scope . onInit = function ( ) {
console . log ( "onInit" ) ;
2014-08-22 11:50:38 +02:00
2014-08-18 17:11:08 +02:00
// Does the room ID provided in the URL?
2014-08-18 17:40:05 +02:00
var room _id _or _alias ;
if ( $routeParams . room _id _or _alias ) {
room _id _or _alias = decodeURIComponent ( $routeParams . room _id _or _alias ) ;
}
if ( room _id _or _alias && '!' === room _id _or _alias [ 0 ] ) {
2014-08-28 16:23:20 +02:00
// Yes. We can go on right now
2014-08-18 17:40:05 +02:00
$scope . room _id = room _id _or _alias ;
2014-11-12 12:14:19 +01:00
$scope . room _alias = modelService . getRoomIdToAliasMapping ( $scope . room _id ) ;
2014-08-18 17:11:08 +02:00
onInit2 ( ) ;
}
else {
2014-08-18 17:40:05 +02:00
// No. The URL contains the room alias. Get this alias.
if ( room _id _or _alias ) {
// The room alias was passed urlencoded, use it as is
$scope . room _alias = room _id _or _alias ;
2014-08-18 17:11:08 +02:00
}
2014-08-18 17:40:05 +02:00
else {
// Else get the room alias by hand from the URL
// ie: extract #public:localhost:8080 from http://127.0.0.1:8000/#/room/#public:localhost:8080
if ( 3 === location . hash . split ( "#" ) . length ) {
$scope . room _alias = "#" + location . hash . split ( "#" ) [ 2 ] ;
}
else {
// In case of issue, go to the default page
console . log ( "Error: cannot extract room alias" ) ;
2014-08-22 11:43:54 +02:00
$location . url ( "/" ) ;
2014-08-18 17:40:05 +02:00
return ;
}
2014-08-18 17:11:08 +02:00
}
// Need a room ID required in Matrix API requests
2014-08-18 17:40:05 +02:00
console . log ( "Resolving alias: " + $scope . room _alias ) ;
2014-08-18 17:11:08 +02:00
matrixService . resolveRoomAlias ( $scope . room _alias ) . then ( function ( response ) {
$scope . room _id = response . data . room _id ;
console . log ( " -> Room ID: " + $scope . room _id ) ;
2014-08-28 16:23:20 +02:00
// Now, we can go on
2014-08-18 17:11:08 +02:00
onInit2 ( ) ;
} ,
function ( ) {
// In case of issue, go to the default page
console . log ( "Error: cannot resolve room alias" ) ;
2014-08-22 11:43:54 +02:00
$location . url ( "/" ) ;
2014-08-18 17:11:08 +02:00
} ) ;
}
} ;
2014-08-28 16:23:20 +02:00
2014-08-18 17:11:08 +02:00
var onInit2 = function ( ) {
2014-08-28 16:23:20 +02:00
console . log ( "onInit2" ) ;
2014-10-31 17:22:15 +01:00
// =============================
$scope . room = modelService . getRoom ( $scope . room _id ) ;
// =============================
2014-08-28 16:23:20 +02:00
2014-09-16 16:13:24 +02:00
// Scroll down as soon as possible so that we point to the last message
// if it already exists in memory
scrollToBottom ( true ) ;
2014-08-28 16:23:20 +02:00
// Make sure the initialSync has been before going further
eventHandlerService . waitForInitialSyncCompletion ( ) . then (
2014-08-12 16:10:52 +02:00
function ( ) {
2014-09-01 16:27:11 +02:00
2014-08-28 16:23:20 +02:00
var needsToJoin = true ;
// The room members is available in the data fetched by initialSync
2014-10-31 18:13:27 +01:00
if ( $scope . room ) {
2014-09-16 15:42:31 +02:00
2014-10-31 18:13:27 +01:00
var messages = $scope . room . events ;
2014-09-25 14:46:11 +02:00
if ( 0 === messages . length
|| ( 1 === messages . length && "m.room.member" === messages [ 0 ] . type && "invite" === messages [ 0 ] . content . membership && $scope . state . user _id === messages [ 0 ] . state _key ) ) {
// If we just joined a room, we won't have this history from initial sync, so we should try to paginate it anyway
$scope . state . first _pagination = true ;
2014-09-18 19:17:27 +02:00
}
else {
2014-09-25 14:46:11 +02:00
// There is no need to do a 1st pagination (initialSync provided enough to fill a page)
$scope . state . first _pagination = false ;
2014-09-18 19:17:27 +02:00
}
2014-09-16 15:42:31 +02:00
2014-10-31 18:13:27 +01:00
var members = $scope . room . current _room _state . members ;
2014-08-12 16:10:52 +02:00
2014-08-28 16:23:20 +02:00
// Update the member list
for ( var i in members ) {
2014-09-24 12:22:40 +02:00
if ( ! members . hasOwnProperty ( i ) ) continue ;
2014-11-06 15:52:22 +01:00
var member = members [ i ] . event ;
2014-08-28 16:23:20 +02:00
updateMemberList ( member ) ;
}
// Check if the user has already join the room
if ( $scope . state . user _id in members ) {
2014-11-06 15:52:22 +01:00
if ( "join" === members [ $scope . state . user _id ] . event . content . membership ) {
2014-08-28 16:23:20 +02:00
needsToJoin = false ;
2014-08-12 16:10:52 +02:00
}
}
2014-08-28 16:23:20 +02:00
}
2014-08-14 18:23:47 +02:00
2014-08-28 16:23:20 +02:00
// Do we to join the room before starting?
if ( needsToJoin ) {
2014-09-09 14:18:08 +02:00
$scope . state . waiting _for _joined _event = true ;
2014-08-28 16:23:20 +02:00
matrixService . join ( $scope . room _id ) . then (
function ( ) {
2014-09-23 19:50:39 +02:00
// TODO: factor out the common housekeeping whenever we try to join a room or alias
matrixService . roomState ( $scope . room _id ) . then (
function ( response ) {
2014-09-23 21:01:11 +02:00
eventHandlerService . handleEvents ( response . data , false , true ) ;
2014-09-23 19:50:39 +02:00
} ,
function ( error ) {
console . error ( "Failed to get room state for: " + $scope . room _id ) ;
}
) ;
2014-09-09 14:18:08 +02:00
// onInit3 will be called once the joined m.room.member event is received from the events stream
// This avoids to get the joined information twice in parallel:
// - one from the events stream
// - one from the pagination because the pagination window covers this event ts
2014-08-28 16:23:20 +02:00
console . log ( "Joined room " + $scope . room _id ) ;
} ,
function ( reason ) {
2014-09-03 11:45:30 +02:00
console . log ( "Can't join room: " + JSON . stringify ( reason ) ) ;
2014-09-23 19:50:39 +02:00
// FIXME: what if it wasn't a perms problem?
2014-09-11 16:54:51 +02:00
$scope . state . permission _denied = "You do not have permission to join this room" ;
2014-08-28 16:23:20 +02:00
} ) ;
}
else {
onInit3 ( ) ;
}
}
) ;
} ;
var onInit3 = function ( ) {
console . log ( "onInit3" ) ;
// Make recents highlight the current room
2014-11-12 15:55:57 +01:00
recentsService . setSelectedRoomId ( $scope . room _id ) ;
2014-09-01 16:21:13 +02:00
2014-09-17 14:38:33 +02:00
// Get the up-to-date the current member list
2014-09-01 16:21:13 +02:00
matrixService . getMemberList ( $scope . room _id ) . then (
function ( response ) {
for ( var i = 0 ; i < response . data . chunk . length ; i ++ ) {
var chunk = response . data . chunk [ i ] ;
updateMemberList ( chunk ) ;
2014-09-02 11:54:11 +02:00
// Add his power level
updateUserPowerLevel ( chunk . user _id ) ;
2014-09-01 16:21:13 +02:00
}
2014-09-02 11:14:45 +02:00
// Arm list timing update timer
updateMemberListPresenceAge ( ) ;
2014-09-09 16:46:30 +02:00
2014-09-16 15:42:31 +02:00
// Allow pagination
2014-09-09 16:46:30 +02:00
$scope . state . can _paginate = true ;
2014-09-16 15:42:31 +02:00
// Do a first pagination only if it is required
// FIXME: Should be no more require when initialSync/{room_id} will be available
if ( $scope . state . first _pagination ) {
paginate ( MESSAGES _PER _PAGINATION ) ;
}
else {
// There are already messages, go to the last message
scrollToBottom ( true ) ;
}
2014-09-01 16:21:13 +02:00
} ,
function ( error ) {
$scope . feedback = "Failed get member list: " + error . data . error ;
}
) ;
2014-08-12 16:10:52 +02:00
} ;
$scope . leaveRoom = function ( ) {
matrixService . leave ( $scope . room _id ) . then (
function ( response ) {
2014-09-24 02:12:59 +02:00
console . log ( "Left room " + $scope . room _id ) ;
2014-08-22 18:08:03 +02:00
$location . url ( "home" ) ;
2014-08-12 16:10:52 +02:00
} ,
2014-08-14 16:47:38 +02:00
function ( error ) {
$scope . feedback = "Failed to leave room: " + error . data . error ;
2014-08-12 16:10:52 +02:00
} ) ;
} ;
2014-08-20 16:18:50 +02:00
$scope . sendImage = function ( url , body ) {
2014-09-05 17:52:11 +02:00
scrollToBottom ( true ) ;
2014-08-20 16:18:50 +02:00
matrixService . sendImageMessage ( $scope . room _id , url , body ) . then (
2014-08-13 11:42:28 +02:00
function ( ) {
console . log ( "Image sent" ) ;
} ,
2014-08-14 16:47:38 +02:00
function ( error ) {
$scope . feedback = "Failed to send image: " + error . data . error ;
2014-08-13 11:42:28 +02:00
} ) ;
} ;
2014-08-14 18:23:47 +02:00
2014-08-14 18:53:05 +02:00
$scope . imageFileToSend ;
$scope . $watch ( "imageFileToSend" , function ( newValue , oldValue ) {
if ( $scope . imageFileToSend ) {
2014-08-21 14:30:41 +02:00
// Upload this image with its thumbnail to Internet
mFileUpload . uploadImageAndThumbnail ( $scope . imageFileToSend , THUMBNAIL _SIZE ) . then (
function ( imageMessage ) {
// imageMessage is complete message structure, send it as is
matrixService . sendMessage ( $scope . room _id , undefined , imageMessage ) . then (
function ( ) {
console . log ( "Image message sent" ) ;
2014-08-20 16:18:50 +02:00
} ,
function ( error ) {
2014-08-21 14:30:41 +02:00
$scope . feedback = "Failed to send image message: " + error . data . error ;
} ) ;
2014-08-14 18:53:05 +02:00
} ,
function ( error ) {
2014-08-21 14:30:41 +02:00
$scope . feedback = "Can't upload image" ;
2014-08-20 16:18:50 +02:00
}
2014-08-14 18:53:05 +02:00
) ;
}
} ) ;
2014-08-14 18:23:47 +02:00
$scope . loadMoreHistory = function ( ) {
2014-08-16 23:05:31 +02:00
paginate ( MESSAGES _PER _PAGINATION ) ;
2014-08-14 18:23:47 +02:00
} ;
2014-08-27 19:57:54 +02:00
2014-11-11 05:39:16 +01:00
$scope . checkWebRTC = function ( ) {
if ( ! $rootScope . isWebRTCSupported ( ) ) {
alert ( "Your browser does not support WebRTC" ) ;
return false ;
}
if ( $scope . memberCount ( ) != 2 ) {
alert ( "WebRTC calls are currently only supported on rooms with two members" ) ;
return false ;
}
return true ;
} ;
$scope . startVoiceCall = function ( ) {
if ( ! $scope . checkWebRTC ( ) ) return ;
2014-08-27 19:57:54 +02:00
var call = new MatrixCall ( $scope . room _id ) ;
2014-09-01 18:15:26 +02:00
call . onError = $rootScope . onCallError ;
call . onHangup = $rootScope . onCallHangup ;
2014-09-17 17:26:35 +02:00
// remote video element is used for playing audio in voice calls
2014-11-12 18:27:41 +01:00
call . remoteVideoSelector = angular . element ( '#remoteVideo' ) [ 0 ] ;
2014-09-17 17:26:35 +02:00
call . placeVoiceCall ( ) ;
2014-09-09 18:37:50 +02:00
$rootScope . currentCall = call ;
} ;
$scope . startVideoCall = function ( ) {
2014-11-11 05:39:16 +01:00
if ( ! $scope . checkWebRTC ( ) ) return ;
2014-09-09 18:37:50 +02:00
var call = new MatrixCall ( $scope . room _id ) ;
call . onError = $rootScope . onCallError ;
call . onHangup = $rootScope . onCallHangup ;
2014-11-07 18:56:28 +01:00
call . localVideoSelector = '#localVideo' ;
call . remoteVideoSelector = '#remoteVideo' ;
2014-09-17 17:26:35 +02:00
call . placeVideoCall ( ) ;
2014-09-01 18:15:26 +02:00
$rootScope . currentCall = call ;
2014-09-05 11:13:33 +02:00
} ;
2014-08-28 20:03:34 +02:00
2014-10-27 17:28:33 +01:00
$scope . openJson = function ( content ) {
2014-11-03 16:02:16 +01:00
$scope . event _selected = angular . copy ( content ) ;
// FIXME: Pre-calculated event data should be stripped in a nicer way.
2014-11-04 11:30:34 +01:00
$scope . event _selected . _ _room _member = undefined ;
$scope . event _selected . _ _target _room _member = undefined ;
2014-11-03 16:02:16 +01:00
2014-10-29 16:02:30 +01:00
// scope this so the template can check power levels and enable/disable
// buttons
2014-11-03 11:23:14 +01:00
$scope . pow = eventHandlerService . getUserPowerLevel ;
2014-10-29 16:02:30 +01:00
2014-10-27 17:28:33 +01:00
var modalInstance = $modal . open ( {
2014-10-29 16:02:30 +01:00
templateUrl : 'eventInfoTemplate.html' ,
controller : 'EventInfoController' ,
scope : $scope
2014-10-27 17:28:33 +01:00
} ) ;
2014-10-29 16:31:50 +01:00
modalInstance . result . then ( function ( action ) {
if ( action === "redact" ) {
var eventId = $scope . event _selected . event _id ;
console . log ( "Redacting event ID " + eventId ) ;
matrixService . redactEvent (
$scope . event _selected . room _id ,
eventId
) . then ( function ( response ) {
console . log ( "Redaction = " + JSON . stringify ( response ) ) ;
} , function ( error ) {
console . error ( "Failed to redact event: " + JSON . stringify ( error ) ) ;
if ( error . data . error ) {
$scope . feedback = error . data . error ;
}
} ) ;
}
} , function ( ) {
// any dismiss code
} ) ;
2014-10-27 17:28:33 +01:00
} ;
2014-10-30 12:14:29 +01:00
$scope . openRoomInfo = function ( ) {
2014-10-30 18:01:17 +01:00
$scope . roomInfo = { } ;
$scope . roomInfo . newEvent = {
content : { } ,
type : "" ,
state _key : ""
} ;
2014-10-31 18:13:27 +01:00
var stateEvents = $scope . room . current _room _state . state _events ;
2014-10-30 17:21:27 +01:00
// The modal dialog will 2-way bind this field, so we MUST make a deep
// copy of the state events else we will be *actually adjusing our view
// of the world* when fiddling with the JSON!! Apparently parse/stringify
// is faster than jQuery's extend when doing deep copies.
2014-10-30 18:01:17 +01:00
$scope . roomInfo . stateEvents = JSON . parse ( JSON . stringify ( stateEvents ) ) ;
2014-10-30 12:14:29 +01:00
var modalInstance = $modal . open ( {
templateUrl : 'roomInfoTemplate.html' ,
controller : 'RoomInfoController' ,
size : 'lg' ,
scope : $scope
} ) ;
} ;
2014-10-29 16:02:30 +01:00
} ] )
. controller ( 'EventInfoController' , function ( $scope , $modalInstance ) {
console . log ( "Displaying modal dialog for >>>> " + JSON . stringify ( $scope . event _selected ) ) ;
$scope . redact = function ( ) {
console . log ( "User level = " + $scope . pow ( $scope . room _id , $scope . state . user _id ) +
2014-10-31 18:13:27 +01:00
" Redact level = " + $scope . room . current _room _state . state _events [ "m.room.ops_levels" ] . content . redact _level ) ;
2014-10-29 16:02:30 +01:00
console . log ( "Redact event >> " + JSON . stringify ( $scope . event _selected ) ) ;
2014-10-29 16:31:50 +01:00
$modalInstance . close ( "redact" ) ;
2014-10-29 16:02:30 +01:00
} ;
2014-11-12 12:40:19 +01:00
$scope . dismiss = $modalInstance . dismiss ;
2014-10-30 12:14:29 +01:00
} )
2014-10-30 17:31:47 +01:00
. controller ( 'RoomInfoController' , function ( $scope , $modalInstance , $filter , matrixService ) {
2014-10-30 12:14:29 +01:00
console . log ( "Displaying room info." ) ;
2014-11-12 18:06:03 +01:00
$scope . userIDToInvite = "" ;
$scope . inviteUser = function ( ) {
matrixService . invite ( $scope . room _id , $scope . userIDToInvite ) . then (
function ( ) {
console . log ( "Invited." ) ;
$scope . feedback = "Invite successfully sent to " + $scope . userIDToInvite ;
$scope . userIDToInvite = "" ;
} ,
function ( reason ) {
$scope . feedback = "Failure: " + reason . data . error ;
} ) ;
} ;
2014-10-30 12:14:29 +01:00
2014-10-30 14:24:40 +01:00
$scope . submit = function ( event ) {
2014-10-30 17:21:27 +01:00
if ( event . content ) {
2014-10-30 17:31:47 +01:00
console . log ( "submit >>> " + JSON . stringify ( event . content ) ) ;
matrixService . sendStateEvent ( $scope . room _id , event . type ,
event . content , event . state _key ) . then ( function ( response ) {
$modalInstance . dismiss ( ) ;
} , function ( err ) {
$scope . feedback = err . data . error ;
}
) ;
2014-10-30 17:21:27 +01:00
}
2014-10-30 14:24:40 +01:00
} ;
2014-10-30 12:14:29 +01:00
$scope . dismiss = $modalInstance . dismiss ;
2014-10-29 16:02:30 +01:00
} ) ;