SmartIT_Extensions/BMC/smart-it-full-helix/components/chat/chat-window.js

570 lines
34 KiB
JavaScript

"use strict";
(function () {
'use strict';
/**
* @ngdoc directive
* @name myitsmApp:chatWindow
* @restrict AE
* @desription Chat windows are used for user chatting. It's a draggable window which contains chat message history and
* chat controls bar. From chat window one can invite other user for a quick discussion of some issue. Also there is an option
* to relate conversation with specific app instance and save chat log to instance timeline, at the end of conversation
*/
angular.module('myitsmApp')
.directive('chatWindow', ['$filter', '$state', 'personModel', 'chatModel', 'searchModel', 'systemAlertService', '$window', 'userModel', '$timeout',
function ($filter, $state, personModel, chatModel, searchModel, systemAlertService, $window, userModel, $timeout) {
return {
restrict: 'AE',
templateUrl: 'components/chat/chat-window.html',
replace: true,
scope: {
chatRoom: "=",
isPopupWindow: "@"
},
link: function (scope, $element) {
var popupMode = !!scope.isPopupWindow;
scope.parentWindow = window.opener; //Will be null if not popup window
function parseStorageString(key, cacheString) {
var storedValue = cacheString || localStorage.getItem(key);
try {
storedValue = JSON.parse(storedValue) || [];
}
catch (e) {
storedValue = [];
}
return storedValue;
}
function onStorageEvent(storageEvent) {
if (storageEvent.key != 'app.popup.setFocusRequest' || storageEvent.newValue != scope.chatRoom.id)
return;
localStorage.setItem('app.popup.setFocusRequest', "");
console.log("Already opened");
}
if (popupMode) {
var popupsList = parseStorageString('app.popup.rooms');
popupsList.push(scope.chatRoom.id);
localStorage['app.popup.rooms'] = JSON.stringify(popupsList);
$window.document.body.style.minWidth = 0;
$window.document.body.style.overflow = "hidden";
$window.addEventListener('storage', onStorageEvent, false);
$window.onbeforeunload = function () {
var activeChatPopups = parseStorageString('app.popup.rooms'), updatedPopupsList = _.without(activeChatPopups, scope.chatRoom.id) || [];
localStorage.setItem('app.popup.rooms', JSON.stringify(updatedPopupsList));
};
}
else
$element.draggable({ containment: "parent", scroll: false, handle: ".drag-handle" });
scope.state = {
searchUserFormActive: false,
searchTicketProfileActive: false,
searchInProgress: false
};
scope.userModel = userModel;
scope.pendingSearchPromises = [];
scope.chatActions = {};
scope.searchBar = { selectedItem: null };
scope.messageEditor = { messageBody: '' };
scope.chatRoom.roster = {};
scope.headerText = "";
var textAreaElement = $element.find(".chat__message-editor");
textAreaElement.focus();
/**
* @ngdoc method
* @name myitsmApp.chatWindow#getChatParticipantsData
* @methodOf myitsmApp.chatWindow
*
* @description
* Fills chat participants with their profiles information. There are two scenarios
* of profile data retrieve - by loginId or by jid values, depending on which of two values is available
*
*/
var getChatParticipantsData = function () {
var chatProfiles = { getProfileForJids: [] };
_.each(scope.chatRoom.participants, function (participant) {
var userJID = Strophe.getNodeFromJid(participant.jid);
if (userJID != chatModel.Client.conn.authcid) {
if (chatModel.cachedProfiles[userJID] && chatModel.cachedProfiles[userJID].firstName) {
scope.chatRoom.roster[userJID] = chatModel.cachedProfiles[userJID];
}
else {
chatProfiles.getProfileForJids.push(userJID);
}
}
});
if (chatProfiles.getProfileForJids.length) {
chatModel.getUserProfileByJID(chatProfiles.getProfileForJids).then(function () {
_.each(chatProfiles.getProfileForJids, function (jid) {
scope.chatRoom.roster[jid] = chatModel.cachedProfiles[jid];
});
});
}
};
/*Watching, when participants list changes*/
scope.$watch(function () {
return scope.chatRoom.participants;
}, function (newValue) {
if (newValue) {
getChatParticipantsData();
}
});
/*Watching, when user adds text in search input*/
scope.$watch('searchBar.searchParam', function (newVal, oldVal) {
if (newVal && (newVal != oldVal)) {
scope.searchBar.handleUserQuery();
}
if (oldVal && !newVal) {
scope.pendingSearchPromises = [];
scope.state.searchInProgress = false;
scope.searchBar.resultsList = null;
}
});
/*Generates participants string, to show on chat window header*/
scope.generateChatHeaderCaption = function () {
if (scope.chatRoom.generateRosterSummary) {
return scope.chatRoom.generateRosterSummary();
}
var profilesList = _.toArray(scope.chatRoom.roster), namesArray = _.map(scope.chatRoom.roster, function (profile) {
if (profilesList.length < 3) {
return profile.fullName || profile.displayName;
}
return profile.firstName;
});
return namesArray.length > 0 ? namesArray.join(", ") : $filter('i18n')('chat.header.generic');
};
/*List of available chat window actions*/
if (window.isRtl) {
scope.chatControls = [
{ label: 'Minimize Chat', iconClass: 'glyphicon glyphicon-minus', name: 'minimize', disabled: scope.isPopupWindow },
{ label: 'Popin Chat', iconClass: 'icon-pop_in', name: 'popinChatWindow', disabled: !scope.isPopupWindow || scope.parentWindow && scope.parentWindow.closed },
{ label: 'Popout Chat', iconClass: 'icon-pop_up', name: 'popoutChatWindow', disabled: scope.isPopupWindow },
{ label: 'Invite User', iconClass: 'icon-user_plus', name: 'showSearchForm', args: 'invite' },
{ label: 'Leave Chat', iconClass: 'icon-exit', name: 'leaveChat' }
];
}
else {
scope.chatControls = [
{ label: 'Leave Chat', iconClass: 'icon-exit', name: 'leaveChat' },
{ label: 'Invite User', iconClass: 'icon-user_plus', name: 'showSearchForm', args: 'invite' },
{ label: 'Popout Chat', iconClass: 'icon-pop_up', name: 'popoutChatWindow', disabled: scope.isPopupWindow },
{ label: 'Popin Chat', iconClass: 'icon-pop_in', name: 'popinChatWindow', disabled: !scope.isPopupWindow || scope.parentWindow && scope.parentWindow.closed },
{ label: 'Minimize Chat', iconClass: 'glyphicon glyphicon-minus', name: 'minimize', disabled: scope.isPopupWindow }
];
}
scope.redirectToAsignedItem = function () {
var relatedObject = scope.chatRoom.parent, url;
if (!relatedObject) {
return false;
}
if (relatedObject.ticketType == EntityVO.TYPE_ASSET) {
if (scope.isPopupWindow) {
url = $state.href(EntityVO.TYPE_ASSET, { assetId: relatedObject.reconciliationId, assetClassId: relatedObject.classId });
window.open(url, "_blank");
return;
}
$state.go(EntityVO.TYPE_ASSET, { assetId: relatedObject.reconciliationId, assetClassId: relatedObject.classId });
}
else {
if (scope.isPopupWindow) {
url = $state.href(relatedObject.type, { id: relatedObject.id });
window.open(url, "_blank");
return;
}
$state.go(relatedObject.type, { id: relatedObject.id });
}
};
/*Sets proper class for chat controls icon*/
scope.getConditionalClasses = function (control) {
if (!chatModel.connected && control.name !== 'minimize') {
return 'chat-disable-events';
}
if (control.name == 'showSearchForm') {
return scope.state.searchUserFormActive ? 'active' : angular.noop();
}
};
/*Chat controls click event transmitter. Uses controls item, to execute applicable click handler */
scope.handleChatControlsClick = function ($e, action) {
if (scope.chatRoom.inactiveRoom)
return false;
return scope.chatActions[action.name](action.args);
};
/*Removes chat window relation to app instance, if one was specified*/
scope.removeChatAssignment = function () {
scope.chatRoom.loadingAssignments = true;
chatModel.removeChatAssignment(scope.chatRoom.room).finally(function () {
scope.chatRoom.loadingAssignments = false;
});
};
scope.chatActions.popoutChatWindow = function () {
var activeChatPopupsList = localStorage.getItem('app.popup.rooms') || "[]", popupURL = '#/chatPopupWindow';
if (activeChatPopupsList.indexOf(scope.chatRoom.id) >= 0) {
localStorage.setItem('app.popup.setFocusRequest', scope.chatRoom.id);
scope.chatActions.minimize();
return;
}
$window.popupDetails = { roomId: scope.chatRoom.id };
var minWidth = 400, minHeight = 500, width = Math.round((screen.width / 3) / 10) * 10, height = Math.round(screen.height * 70 / 100), popupHeight = _.max([minHeight, height]), popupWidth = _.max([minWidth, width]), popupName = "chatPopup_" + (Strophe.getNodeFromJid(scope.chatRoom.id) || "").split("-").join("_"), popupParams = "location=no, scrollbars=no, resizable=yes, width=" + popupWidth + ", height=" + popupHeight, popup = $window.open(popupURL, popupName, popupParams);
popup.focus();
scope.chatActions.minimize();
};
scope.chatActions.popinChatWindow = function () {
var parentScope, activeChatRoom;
if (scope.parentWindow.closed)
return;
try {
parentScope = scope.parentWindow.angular.element('body').scope();
activeChatRoom = _.find(parentScope.chatModel.activeChatRooms, { id: scope.chatRoom.id });
}
catch (e) { }
if (parentScope && activeChatRoom) {
parentScope.$apply(function () {
activeChatRoom.isOpened = true;
});
window.close();
}
};
/*Minimizes chat window */
scope.chatActions.minimize = function () {
scope.chatRoom.isOpened = false;
};
/**
* @ngdoc method
* @name myitsmApp.chatWindow#leaveChat
* @methodOf myitsmApp.chatWindow
*
* @description
* Action can be triggered from chat controls, by clicking leave chat button. It prompts
* confirmation modal with available actions based on user privileges within the chat
* If chat creator leaves chat it will lead to destroy of the chat windows for all participants
* Also owner can save chat log to a timeline of related app instance, if one was selected
*
*/
scope.chatActions.leaveChat = function () {
if (scope.chatRoom.isLoading)
return;
var nick = Strophe.escapeNode(scope.chatRoom.room.nick), affiliation = scope.chatRoom.participants[nick].affiliation, modalSettings;
var modalText = $filter('i18n')('chat.participantLeave.modal.confirmAction.leave'), modalConfirm = { leave: true };
if (affiliation == 'owner') {
if (scope.chatRoom.parent) {
modalText = $filter('i18n')('chat.ownerLeave.modal.confirmAction.saveLog');
modalConfirm = { save: true };
}
else {
modalText = $filter('i18n')('chat.ownerLeave.modal.confirmAction.leave');
modalConfirm = { close: true };
}
}
modalSettings = {
title: $filter('i18n')('chat.ownerLeave.modal.title'),
text: modalText,
buttons: [
{
text: $filter('i18n')('common.labels.leave'),
data: modalConfirm
},
{
text: $filter('i18n')('common.button.cancel')
}
],
isPopoutWindow: true
};
var modalInstance = systemAlertService.modal(modalSettings);
modalInstance.result.then(function (data) {
if (!_.isEmpty(data)) {
var userAction = data ? (data.save ? 'saveLog' : 'destroyRoom') : 'leave';
if (scope.isPopupWindow) {
var parentScope, activeChatRoom;
try {
parentScope = scope.parentWindow.angular.element('body').scope();
activeChatRoom = _.find(parentScope.chatModel.activeChatRooms, { id: scope.chatRoom.id });
if (parentScope && activeChatRoom) {
parentScope.$apply(function () {
activeChatRoom.isOpened = false;
(userAction == 'saveLog') && parentScope.chatModel.saveChatLogToTicketWorknote(activeChatRoom, activeChatRoom.parent);
(userAction != 'leave') && parentScope.chatModel.leaveRoomAndDestroyChatWindow(activeChatRoom);
(userAction == 'leave') && parentScope.chatModel.leaveConversation(activeChatRoom);
});
}
}
catch (e) { }
window.close();
}
else {
scope.chatRoom.isOpened = false;
(userAction == 'saveLog') && chatModel.saveChatLogToTicketWorknote(scope.chatRoom, scope.chatRoom.parent);
(userAction != 'leave') && chatModel.leaveRoomAndDestroyChatWindow(scope.chatRoom);
(userAction == 'leave') && chatModel.leaveConversation(scope.chatRoom);
}
}
});
};
/**
* @ngdoc method
* @name myitsmApp.chatWindow#showSearchForm
* @methodOf myitsmApp.chatWindow
*
* @description
* Shows search bar to the user, when one clicks invite person button or relate chat link. Controls search context,
* based clicked item.
*
*/
scope.chatActions.showSearchForm = function (type) {
if (scope.chatRoom.isLoading)
return;
if (type == 'connect') {
scope.searchBar.clearSelection();
scope.state.searchTicketProfileActive = true;
scope.state.searchUserFormActive = false;
}
else if (type == 'invite') {
if (!scope.state.searchUserFormActive) {
scope.searchBar.searchParam = null;
scope.searchBar.clearSelection();
scope.state.searchUserFormActive = true;
scope.state.searchTicketProfileActive = false;
scope.filteredList = [];
}
else {
scope.searchBar.clearSelection();
scope.state.searchUserFormActive = false;
}
}
$timeout(function () {
try {
$element.find("[ng-model*='searchParam']")[0].focus();
}
catch (ex) { }
}, 500);
};
scope.searchBar.suggestionMouseOver = function (index) {
var hoveredItem = scope.filteredList[index];
if (scope.state.searchUserFormActive && hoveredItem.active != EntityVO.CHAT_STATUS_ONLINE) {
return false;
}
scope.searchBar.alignWithTop = null;
scope.searchBar.selectedSuggestionIndex = index;
};
/*Keydown event handler for search input. Clears and hides search input when Esc is pressed*/
scope.searchBar.checkPressedKey = function ($event) {
var keyCode = $event.keyCode || $event.which;
if (keyCode == 27) {
scope.searchBar.clearSelection();
scope.state.searchUserFormActive = false;
scope.state.searchTicketProfileActive = false;
}
if (keyCode == 13) {
$event.preventDefault();
var selectedItem = scope.filteredList[scope.searchBar.selectedSuggestionIndex];
if (!selectedItem || (scope.state.searchUserFormActive && selectedItem.available != EntityVO.CHAT_STATUS_ONLINE)) {
return false;
}
else {
scope.searchBar.selectSuggestion(selectedItem);
}
}
if (keyCode == 38 || keyCode == 40) {
if (scope.state.searchUserFormActive || scope.state.searchTicketProfileActive && scope.filteredList && scope.searchBar.resultsList.length > 0) {
$event.preventDefault();
if (keyCode == 38) {
if (scope.searchBar.selectedSuggestionIndex == 0)
return false;
else {
scope.searchBar.alignWithTop = true;
scope.searchBar.selectedSuggestionIndex--;
}
return false;
}
else {
if (scope.searchBar.selectedSuggestionIndex == scope.filteredList.length - 1)
return false;
else {
var nextIndex = scope.searchBar.selectedSuggestionIndex + 1, nextItem = scope.filteredList[nextIndex];
if (scope.state.searchUserFormActive && nextItem && nextItem.available != EntityVO.CHAT_STATUS_ONLINE)
return false;
else {
scope.searchBar.alignWithTop = false;
scope.searchBar.selectedSuggestionIndex++;
}
}
return false;
}
}
}
return true;
};
/**
* Handles user search text input and executes server request to retrieve user profiles or instance list, if search text
* pass length check
* */
scope.searchBar.handleUserQuery = function () {
var query = scope.searchBar.searchParam;
if (scope.searchBar.selectedItem) {
return;
}
if (!query) {
scope.searchBar.selectedSuggestionIndex = 0;
scope.searchBar.resultsList = [];
scope.filteredList = [];
return;
}
if (query.length >= 3) {
scope.searchBar.selectedSuggestionIndex = 0;
scope.state.searchInProgress = true;
scope.getSearchResultsDebounce(query);
}
else {
scope.searchBar.resultsList = [];
scope.filteredList = [];
}
};
/*Controls placeholder value, based on context of search, selected by user*/
scope.searchBar.getSearchBarPlaceholder = function () {
if (scope.state.searchUserFormActive) {
return $filter('i18n')('chat.searchBar.userSearch.placeholder');
}
else if (scope.state.searchTicketProfileActive) {
return $filter('i18n')('chat.searchBar.ticketProfile.placeholder');
}
};
/*
* Handles item selection from suggestions list, returned from the server.
* Fills search input with selected value. Next step should be confirmation or cancel
* of selection
* */
scope.searchBar.selectSuggestion = function (item) {
if (item.hasOwnProperty('firstName') || item.hasOwnProperty('loginId')) {
var available = item.available ? item.available.toLowerCase() : EntityVO.CHAT_STATUS_OFFLINE;
if (available != EntityVO.CHAT_STATUS_ONLINE) {
return;
}
}
scope.searchBar.selectedItem = item;
scope.searchBar.confirmSelection();
$timeout(function () {
try {
$element.find(".chat__search-bar_confirm-action")[0].focus();
}
catch (ex) { }
}, 500);
};
/*Cancels suggested item selction*/
scope.searchBar.clearSelection = function () {
scope.searchBar.selectedItem = null;
scope.searchBar.searchParam = '';
scope.searchBar.resultsList = [];
};
/**
* Confirms selection and based on context
* relate chat to app instance or invites user to chat
*/
scope.searchBar.confirmSelection = function () {
if (scope.state.searchUserFormActive) {
scope.chatRoom.roster[scope.searchBar.selectedItem.jid] = scope.searchBar.selectedItem;
chatModel.inviteUserToChat(scope.searchBar.selectedItem, scope.chatRoom);
scope.state.searchUserFormActive = false;
}
else {
chatModel.assignChatRoom(scope.chatRoom.room, scope.searchBar.selectedItem);
scope.chatRoom.parent = scope.searchBar.selectedItem;
scope.state.searchTicketProfileActive = false;
}
scope.searchBar.clearSelection();
};
/*Used in ngRepeat as sorting function in suggestions list of invitees*/
scope.sortResults = function (item) {
if (scope.state.searchUserFormActive) {
!!item.available ? item.available = item.available.toLowerCase() : item.available = EntityVO.CHAT_STATUS_OFFLINE;
var statusIndex = chatModel.Client.options.users_status_list.indexOf(item.available);
return statusIndex + (item.fullName || item.firstName + " " + item.lastName);
}
};
/*Debounce function, which runs request to a server to get profiles or instances matching
* user search criteria.
* It will execute server request only when user stops typing or makes a pause in typing equal to 400 ms
* Data returned from server will be used to build suggestions list.
* */
scope.getSearchResultsDebounce = _.debounce(function (query) {
var promise;
if (scope.state.searchUserFormActive) {
promise = personModel.getListOfPersonByName(query)
.then(function (profiles) {
chatModel.updateCachedProfiles(profiles);
scope.filteredList = [];
if (scope.searchBar.searchParam && !scope.searchBar.selectedItem && scope.state.searchUserFormActive) {
var participants = scope.chatRoom.getParticipantJIDsArray();
scope.searchBar.resultsList = _.filter(profiles, function (profile) {
var currentUserMatch = (chatModel.currentUser.loginId == profile.loginId);
if (profile.jid) {
var chatParticipant = (participants.indexOf(profile.jid) >= 0);
}
return !chatParticipant && !currentUserMatch;
});
}
});
}
else {
var filters = { types: [EntityVO.TYPE_INCIDENT, EntityVO.TYPE_WORKORDER, EntityVO.TYPE_SERVICEREQUEST, EntityVO.TYPE_TASK, EntityVO.TYPE_ASSET, EntityVO.TYPE_CHANGE, EntityVO.TYPE_KNOWNERROR, EntityVO.TYPE_PROBLEM, EntityVO.TYPE_RELEASE] };
promise = searchModel.getGlobalSearchResults(query, filters)
.then(function () {
if (scope.pendingSearchPromises.length == 0) {
return;
}
if (scope.searchBar.searchParam && !scope.searchBar.selectedItem && scope.state.searchTicketProfileActive) {
if (searchModel.globalSearchResults.totalCount > 0) {
scope.searchBar.resultsList = [];
var matchedItems = searchModel.globalSearchResults.items;
_.each(matchedItems, function (item) {
scope.searchBar.resultsList = scope.searchBar.resultsList.concat(item.results);
});
}
else {
scope.searchBar.resultsList = [];
}
}
});
}
scope.pendingSearchPromises.push(promise);
promise.finally(function () {
var promiseIndex = scope.pendingSearchPromises.indexOf(promise);
if (promiseIndex >= 0) {
scope.pendingSearchPromises.splice(promiseIndex, 1);
}
if (scope.pendingSearchPromises.length == 0) {
scope.state.searchInProgress = false;
}
});
}, 400);
scope.tools = function () {
chatModel.processChatMessage(EntityVO.CHATOPS_TOOLS, scope.chatRoom);
};
/*
* keypress event handler, which looks for enter button click and sends
* new chat message, if shift button is not active
* */
scope.messageEditor.handleTyping = function ($event) {
var keyCode = $event.which || $event.keyCode;
if (keyCode == 13 && !$event.shiftKey && scope.messageEditor.messageBody) {
var messageText = scope.messageEditor.messageBody;
if (scope.chatRoom.parent != null && scope.chatRoom.parent.type == 'incident') {
var chatR = scope.chatRoom;
if (messageText == EntityVO.CHATOPS_DETAILS || messageText == EntityVO.CHATOPS_KNOWLEDGE
|| messageText == EntityVO.CHATOPS_SIMILARINCIDENTS || messageText == EntityVO.CHATOPS_SWARM || messageText == EntityVO.CHATOPS_TOOLS) { // chatModel.sendChatMessage(scope.messageEditor.messageBody, scope.chatRoom.room);
chatModel.processChatMessage(messageText, chatR);
}
else {
chatModel.sendChatMessage(scope.messageEditor.messageBody, scope.chatRoom.room);
}
}
else {
chatModel.sendChatMessage(scope.messageEditor.messageBody, scope.chatRoom.room);
}
scope.messageEditor.messageBody = '';
$event.preventDefault();
}
};
scope.isChatEnabled = function () {
return chatModel.connected;
};
}
};
}]);
}());