Commit d6b81cee authored by Vladimir Baranov's avatar Vladimir Baranov

rref #5392. Add popup dialogs, add basic send message funtionality

parent e48eee72
angular.module("ulakbus.messaging") angular.module("ulakbus.messaging")
.directive('messaging', function (Generator, MessagingService, $log, $rootScope) { .directive('messaging', function (Generator, MessagingService, $log, $rootScope, MessagingPopup) {
function getKey (channel) {
var channelKey = channel.channel_key;
if (channel.hasOwnProperty('key')){
channelKey = channel.key; // direct channel
}
return channelKey;
}
return { return {
templateUrl: 'components/messaging/templates/index.html', templateUrl: 'components/messaging/templates/index.html',
restrict: 'E', restrict: 'E',
replace: true, replace: true,
scope: {}, scope: {},
link: function(iScope, iElem, iAttrs){
var popupRootElement = $(iElem).find('.popup-placeholder');
iScope.deleteConfirmation = function(){
return MessagingPopup.show({
templateUrl: "components/messaging/templates/delete_confirmation.html",
rootElement: popupRootElement
})
};
iScope.searchUser = function(){
MessagingPopup.show({
templateUrl: "components/messaging/templates/search_user.html",
rootElement: popupRootElement,
link: function(scope){
scope.onChange = function(query){
scope.loading = true;
scope.searchResult = [];
MessagingService.search_user(query)
.then(function(users){
scope.searchResult = users;
})
.finally(function(){
scope.loading = false;
})
};
scope.onChange("");
}
}).then(function(user){
return iScope.createDirectChannel(user);
});
};
iScope.createChannel = function(){
MessagingPopup.show({
templateUrl: "components/messaging/templates/create_channel.html",
rootElement: popupRootElement,
link: function(scope){
scope.channel = {};
}
}).then(function(channel){
return MessagingService.create_channel(channel.name, channel.description);
});
};
},
controller: function ($scope) { controller: function ($scope) {
$scope.hidden = false; $scope.hidden = false;
$scope.showSearch = { // shared object to populate models through scopes
user: false $scope.shared = {};
};
MessagingService.list_channels().then(function (groupedChannels) { MessagingService.list_channels().then(function (groupedChannels) {
$scope.publicChannels = groupedChannels[MessagingService.CHANNEL_TYPE.PUBLIC]; $scope.publicChannels = groupedChannels[MessagingService.CHANNEL_TYPE.PUBLIC];
...@@ -25,18 +80,49 @@ angular.module("ulakbus.messaging") ...@@ -25,18 +80,49 @@ angular.module("ulakbus.messaging")
var key = user[1]; var key = user[1];
MessagingService.create_direct_channel(key) MessagingService.create_direct_channel(key)
.then(function(result){ .then(function(result){
$log.info("Channel for user ", user[0], "created: ", result); return selectChannel(result.channel_key);
}); });
}; };
$scope.createDirectChannel = this.createDirectChannel;
$scope.hideApp = function(){ $scope.hideApp = function(){
$scope.hidden = true; $scope.hidden = true;
}; };
function selectChannel(channelKey){
$scope.loadingChannel = true;
return MessagingService.show_channel(channelKey).then(function(result){
return result;
}).finally(function(){
$scope.loadingChannel = false;
})
}
$scope.selectChannel = function(channel){ $scope.selectChannel = function(channel){
$scope.activeChannel = channel; var channelKey = getKey(channel);
$scope.selectedChannel = channel;
selectChannel(channelKey).then(function(result){
channel.messages = result.last_messages;
});
} }
$scope.sendMessage = function(content){
if (!content) return;
var channelKey = getKey($scope.selectedChannel);
// select message type: 2 - direct message, 4 - channel message;
var msgType = $scope.selectedChannel.type == MessagingService.CHANNEL_TYPE.DIRECT ? 2 : 4;
MessagingService.create_message(channelKey, msgType, content).then(function(result){
$scope.shared.message = "";
});
}
} }
}; };
}) })
.filter('fromNow', function(Moment){
return function(datetime){
return Moment(datetime).fromNow();
}
});
angular.module("ulakbus.messaging")
.directive("messagingSearchUser", function(MessagingService, $log){
return {
templateUrl: "components/messaging/templates/search_user.html",
restrict: 'E',
replace: true,
require: "^messaging",
scope: {
'showSearch': '=ngShow'
},
link: function(iScope, iElem, iAttrs, messagingCtrl){
iScope.selectUser = function(user){
messagingCtrl.createDirectChannel(user);
iScope.hide();
}
},
controller: function($scope){
$scope.searchResult = [];
$scope.hide = function(){
$scope.showSearch = false;
$scope.searchResult = [];
}
function search(query){
$scope.loading = true;
$scope.searchResult = [];
MessagingService.search_user(query)
.then(function(users){
$scope.searchResult = users;
})
.finally(function(){
$scope.loading = false;
})
}
$scope.onChange = function(query){
search(query);
};
}
}
})
...@@ -44,6 +44,16 @@ angular.module('ulakbus.messaging', ['ui.bootstrap']) ...@@ -44,6 +44,16 @@ angular.module('ulakbus.messaging', ['ui.bootstrap'])
}) })
} }
// prepare messages for UI
function prepareMessages (messages){
for (var i = 0; i < messages.length; i++){
var message = messages[i];
// FIXME: process timezone properly
var ts = message.timestamp.replace(/\.0+Z$/, "");
message.moment = Moment(ts);
}
}
msg.list_channels = function list_channels (){ msg.list_channels = function list_channels (){
/** /**
* request channels list as below; * request channels list as below;
...@@ -194,6 +204,7 @@ angular.module('ulakbus.messaging', ['ui.bootstrap']) ...@@ -194,6 +204,7 @@ angular.module('ulakbus.messaging', ['ui.bootstrap'])
}; };
return wsRequest(outgoing).then(function(result){ return wsRequest(outgoing).then(function(result){
$log.info("Show channel ", channelKey, ": ", result); $log.info("Show channel ", channelKey, ": ", result);
prepareMessages(result.last_messages);
return result; return result;
}) })
}; };
...@@ -225,10 +236,14 @@ angular.module('ulakbus.messaging', ['ui.bootstrap']) ...@@ -225,10 +236,14 @@ angular.module('ulakbus.messaging', ['ui.bootstrap'])
msg.create_message = function(channelKey, msgType, body, attachments){ msg.create_message = function(channelKey, msgType, body, attachments){
var outgoing = { var outgoing = {
view: '_zops_create_message', view: '_zops_create_message',
channel: channelKey, message: {
type: msgType, channel: channelKey,
body: body, type: msgType,
attachments: attachments title: "",
receiver: "",
body: body,
attachments: attachments
}
}; };
return wsRequest(outgoing).then(function(result){ return wsRequest(outgoing).then(function(result){
...@@ -334,4 +349,44 @@ angular.module('ulakbus.messaging', ['ui.bootstrap']) ...@@ -334,4 +349,44 @@ angular.module('ulakbus.messaging', ['ui.bootstrap'])
} }
return msg; return msg;
})
.service("MessagingPopup", function($q, $compile, $http, $rootScope){
function compile(template, config){
var resultDeferred = $q.defer();
var scope = config.scope || $rootScope.$new(true);
var rootElement = config.rootElement;
var element = $compile(template)(scope);
if (config.link){
config.link(scope);
}
scope.done = function(result){
resultDeferred.resolve.apply(this, arguments);
};
scope.cancel = function(){
resultDeferred.reject.apply(this, arguments);
};
rootElement.empty();
rootElement.append(element);
resultDeferred.promise._done = scope.done;
resultDeferred.promise._cancel = scope.cancel;
return resultDeferred.promise.finally(function(){
rootElement.empty();
scope.$destroy();
});
}
this.show = function(config){
if (config.templateUrl){
return $http({method: 'GET', url: config.templateUrl, cache: true}).then(function(result){
return compile(result.data, config)
});
}
return compile(config.template, config);
};
}); });
<div class="chat-popup-window add-user-unit" style="display:block;">
<div class="close-chat-popup-window"><span class="glyphicon glyphicon-remove"></span></div>
<h3>Add User/Unit</h3>
<div class="text-center">
<input type="text" placeholder="Search User/Unit to Add"><br>
</div>
</div>
<span class="loader" ng-show="loadingChannel"></span>
<div class="conversation-section" ng-hide="selectedChannel">
<div class="conversation-body" >
<div class="conversation-body-inner">
<div class="beginning-of-conversation">
Select or create channel to begin conversation
</div>
</div>
</div>
</div>
<div class="conversation-section" ng-hide="loadingChannel || !selectedChannel">
<div class="conversation-header">
<div class="conversation-user">
<div class="user-photo" ng-show="selectedChannel.avatar_url"><img ng-src="{{selectedChannel.avatar_url}}"></div>
<div class="user-name">{{selectedChannel.name}}</div>
</div>
<div class="conversation-search">
<input type="text" placeholder="Arama Yap">
</div>
<div class="dropdown">
<div class="chat-app-actions dropdown-toggle" data-toggle="dropdown" id="chat-app-actions">
<span class="glyphicon glyphicon-option-vertical"></span>
</div>
<ul class="dropdown-menu" ng-show="selectedChannel.actions.length > 0" aria-labelledby="chat-app-actions" style="left: inherit; top: 53px; right: 66px;" >
<li><a ng-click="applyChannelAction(action)" ng-repeat="action in selectedChannel.actions">{{action[0]}}</a></li>
</ul>
</div>
<div class="close-chat-app">
<span class="glyphicon glyphicon-remove" ng-click="hideApp()"></span>
</div>
</div>
<div class="conversation-body">
<div class="conversation-body-inner">
<div class="beginning-of-conversation">
This is the beginning of the conversation
</div>
<div class="conversation-block clearfix" ng-repeat="msg in selectedChannel.messages">
<div class="conversation-actions">
<div class="action"><span class="glyphicon glyphicon-star-empty"></span></div>
<div class="action dropdown-toggle" data-toggle="dropdown">
<span class="glyphicon glyphicon-option-horizontal"></span>
</div>
<ul class="dropdown-menu" style="left:-86px;">
<li><a href="#">Edit</a></li>
<li><a href="#" ng-click="deleteConfirmation()">Delete</a></li>
</ul>
</div>
<div class="user-photo">
<img src="../../../img/erkan.jpg">
</div>
<div class="user-message">
<div class="message-header clearfix">
<div class="user-name">{{::msg.sender_name}}</div>
<div class="message-time">{{::msg.moment|fromNow}}</div>
</div>
<div class="message-content">
{{::msg.content}}
</div>
</div>
</div>
</div>
</div>
<div class="conversation-footer">
<textarea placeholder="Mesajını buraya yaz..." ng-model="shared.message" on-enter-pressed="sendMessage(shared.message)"></textarea>
<div class="add-attachment">
<span class="glyphicon glyphicon-send" ng-click="sendMessage(shared.message);"></span>
<div class="dropup" style="float:left;">
<span class="glyphicon glyphicon-paperclip dropdown-toggle" data-toggle="dropdown" id="attachment"></span>
<ul class="dropdown-menu" aria-labelledby="attachment" style="left:-104px;">
<li><a href="#">Image</a></li>
<li><a href="#">File</a></li>
</ul>
</div>
</div>
</div>
</div>
<div class="chat-popup-window create-new-channel-window">
<div class="close-chat-popup-window" ng-click="cancel()"><span class="glyphicon glyphicon-remove"></span></div>
<h3>Create New Channel</h3>
<div class="text-center">
<input type="text" placeholder="Channel Name" ng-model="channel.name"><br>
<textarea placeholder="Channel Description" ng-model="channel.description"></textarea>
<button class="btn btn-success" ng-click="done(channel)">Create</button>
</div>
</div>
<div class="chat-popup-window confirmation-window">
<div class="close-chat-popup-window" ng-click="cancel()"><span class="glyphicon glyphicon-remove"></span></div>
<div class="text-center" style="margin-top: 150px;font-size: 26px;">
<p>Are you sure you want to delete?</p>
<button class="btn btn-success" style="font-size: 20px;" ng-click="done()">Yes</button>
<button class="btn btn-default" style="font-size: 20px;" ng-click="cancel()">Cancel</button>
</div>
</div>
...@@ -10,75 +10,20 @@ ...@@ -10,75 +10,20 @@
</ul> </ul>
<ul class="channels"> <ul class="channels">
<li class="title">CHANNELS <span class="add-action glyphicon glyphicon-plus-sign"></span></li> <li class="title">CHANNELS <span class="add-action glyphicon glyphicon-plus-sign" ng-click="createChannel()"></span></li>
<li class="notification" ng-repeat="ch in publicChannels">{{ch.name}}</li> <li ng-class="{'notification': ch.unread, 'active': selectedChannel == ch}" ng-repeat="ch in publicChannels" ng-click="selectChannel(ch)">{{ch.name}}</li>
</ul> </ul>
<ul class="direct-messages"> <ul class="direct-messages">
<li class="title">DIRECT MESSAGES <span class="add-action glyphicon glyphicon-plus-sign" ng-click="showSearch.user = true"></span></li> <li class="title">DIRECT MESSAGES <span class="add-action glyphicon glyphicon-plus-sign" ng-click="searchUser()"></span></li>
<li ng-class="{'notification': userChannel.unread}" ng-repeat="userChannel in directChannels">{{userChannel.name}}</li> <li ng-class="{'notification': userChannel.unread, 'active': selectedChannel == userChannel}" ng-repeat="userChannel in directChannels" ng-click="selectChannel(userChannel)">{{userChannel.name}}</li>
</ul> </ul>
</div> </div>
<div class="conversation-section"> <ng-include src="'components/messaging/templates/conversation.html'"></ng-include>
<div class="conversation-header"> <div class="popup-placeholder"></div>
<div class="conversation-user">
<div class="user-photo"><img src="../../../img/erkan.jpg"></div>
<div class="user-name">Erkan Öğümsöğütlü</div>
</div>
<div class="conversation-search">
<input type="text" placeholder="Arama Yap">
</div>
<div class="close-chat-app" ng-click="hideApp()">
<span class="glyphicon glyphicon-remove"></span>
</div>
</div>
<div class="conversation-body">
<div class="conversation-body-inner">
<div class="beginning-of-conversation">
This is the beginning of the conversation
</div>
<div class="conversation-block clearfix">
<div class="conversation-actions">
<div class="action">Edit</div>
<div class="action">Delete</div>
</div>
<div class="user-photo">
<img src="../../../img/erkan.jpg">
</div>
<div class="user-message">
<div class="message-header clearfix">
<div class="user-name">Erkan Öğümsöğütlü</div>
<div class="message-time">13:16</div>
</div>
<div class="message-content">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer purus mauris.
</div>
</div>
</div>
</div>
</div>
<div class="conversation-footer">
<textarea placeholder="Mesajını buraya yaz..."></textarea>
<div class="add-attachment">
<span class="glyphicon glyphicon-file"></span>
<span class="glyphicon glyphicon-picture"></span>
</div>
</div>
</div>
<messaging-search-user ng-show="showSearch.user"></messaging-search-user>
</div> </div>
</div> </div>
......
<div class="create-new-message-window"> <div class="chat-popup-window create-new-message-window">
<div class="close-create-new-message-window" ng-click="hide()"><span class="glyphicon glyphicon-remove"></span></div> <div class="close-chat-popup-window" ng-click="cancel()"><span class="glyphicon glyphicon-remove"></span></div>
<input type="text" placeholder="Birini ismi ile ara..." ng-model="query" ng-change="onChange(query)"> <input type="text" placeholder="Birini ismi ile ara..." ng-model="query" ng-change="onChange(query)">
<div class="search-results"> <div class="search-results">
<span class="loader" ng-show="loading"></span> <span class="loader" ng-show="loading"></span>
<div class="user" ng-repeat="user in searchResult" ng-click="selectUser(user)"> <div class="user" ng-repeat="user in searchResult" ng-click="done(user)">
<img ng-src="{{user[2]}}" ng-show="user[2]"> <img ng-src="{{user[2]}}" ng-show="user[2]">
<div class="user-name">{{user[0]}}</div> <div class="user-name">{{user[0]}}</div>
</div> </div>
......
...@@ -663,19 +663,20 @@ angular.module('ulakbus') ...@@ -663,19 +663,20 @@ angular.module('ulakbus')
} }
} }
}) })
.directive('timetableActionSelector', function($timeout){ .directive('timetableActionSelector', function($timeout) {
// Display/hide popover with actions // Display/hide popover with actions
// global listener used to close popover when user clicks outside of the popover // global listener used to close popover when user clicks outside of the popover
$('html').on('click', function(e) { $('html').on('click', function (e) {
var target = $(e.target); var target = $(e.target);
if (target.parents().is('.action-selector')){ if (target.parents().is('.action-selector')) {
target.parents('.action-selector').children('.popover').toggleClass('ng-hide'); target.parents('.action-selector').children('.popover').toggleClass('ng-hide');
return; return;
} }
if (target.hasClass('action-selector')){ if (target.hasClass('action-selector')) {
target.children('.popover').toggleClass('ng-hide'); target.children('.popover').toggleClass('ng-hide');
return; return;
}; }
;
$('.course-prg-scheduler .action-selector>.popover').toggleClass('ng-hide', true); $('.course-prg-scheduler .action-selector>.popover').toggleClass('ng-hide', true);
}); });
...@@ -685,29 +686,29 @@ angular.module('ulakbus') ...@@ -685,29 +686,29 @@ angular.module('ulakbus')
externalModel: '=ngModel', externalModel: '=ngModel',
onChange: "&ngChange" onChange: "&ngChange"
}, },
link: function(iScope, iElem, iAttrs){ link: function (iScope, iElem, iAttrs) {
var valueToClassMap = { var valueToClassMap = {
1: 'action-indicator_appropriate', 1: 'action-indicator_appropriate',
2: 'action-indicator_uncertain', 2: 'action-indicator_uncertain',
3: 'action-indicator_busy' 3: 'action-indicator_busy'
}; };
if (iAttrs.hasOwnProperty('readonly')){ if (iAttrs.hasOwnProperty('readonly')) {
iAttrs.$observe('readonly', function(v){ iAttrs.$observe('readonly', function (v) {
if (v && v == 'false') v = false; if (v && v == 'false') v = false;
iScope.readonly = v; iScope.readonly = v;
}); });
} }
iScope.$watch('externalModel', function(value){ iScope.$watch('externalModel', function (value) {
iScope.value = valueToClassMap[value]; iScope.value = valueToClassMap[value];
}); });
iScope.setModelValue = function(value){ iScope.setModelValue = function (value) {
var oldValue = iScope.externalModel; var oldValue = iScope.externalModel;
iScope.externalModel = value; iScope.externalModel = value;
// call change in next digest // call change in next digest
$timeout(function(){ $timeout(function () {
if (iScope.onChange && value != oldValue) { if (iScope.onChange && value != oldValue) {
iScope.onChange(); iScope.onChange();
} }
...@@ -716,4 +717,25 @@ angular.module('ulakbus') ...@@ -716,4 +717,25 @@ angular.module('ulakbus')
} }
} }
} }
})
/**
* @memberof ulakbus
* @ngdoc directive
* @name onEnterPressed
* @description Fire action when enter pressed on element
*/
.directive("onEnterPressed", function () {
return {
link: function (scope, element, attrs) {
element.bind("keydown keypress", function (event) {
if(event.which === 13 && !event.ctrlKey) {
scope.$apply(function (){
scope.$eval(attrs.onEnterPressed);
});
event.preventDefault();
}
})
}
}
}); });
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment