From 318578d535da7901c573195eaaae33f1809ebe27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Esp=C3=ADndula?= Date: Fri, 1 Mar 2019 13:13:07 -0300 Subject: [PATCH 01/45] Add chat-message component --- .../chat/message/chat-message.component.js | 35 +++++++++++++++++++ .../components/chat/message/chat-message.css | 28 +++++++++++++++ .../components/chat/message/chat-message.html | 4 +++ 3 files changed, 67 insertions(+) create mode 100644 webchat/components/chat/message/chat-message.component.js create mode 100644 webchat/components/chat/message/chat-message.css create mode 100644 webchat/components/chat/message/chat-message.html diff --git a/webchat/components/chat/message/chat-message.component.js b/webchat/components/chat/message/chat-message.component.js new file mode 100644 index 000000000..84cc30538 --- /dev/null +++ b/webchat/components/chat/message/chat-message.component.js @@ -0,0 +1,35 @@ +(function () { + 'use strict'; + + angular.module("webchat").component("chatMessage", { + templateUrl: "app/components/chat/message/chat-message.html", + controller: [ + 'AuthService', + chatMessageController, + ], + controllerAs: "chatMessageCtrl", + bindings: { + messageObj: '<', + }, + }); + + function chatMessageController(AuthService) { + const chatMessageCtrl = this; + + + chatMessageCtrl.formatTimestamp = (timestamp) => { + return timestamp.getHours() + ":" + timestamp.getMinutes(); + }; + + chatMessageCtrl.$onInit = () => { + const timestamp = new Date(chatMessageCtrl.messageObj.timestamp); + chatMessageCtrl.formattedTimestamp = chatMessageCtrl.formatTimestamp(timestamp); + }; + + chatMessageCtrl.messageSent = () => { + const result = chatMessageCtrl.messageObj.sender === AuthService.getCurrentUser().key; + return result; + }; + } + +})(); \ No newline at end of file diff --git a/webchat/components/chat/message/chat-message.css b/webchat/components/chat/message/chat-message.css new file mode 100644 index 000000000..0c244fb3e --- /dev/null +++ b/webchat/components/chat/message/chat-message.css @@ -0,0 +1,28 @@ +.chat-message__container { + width: fit-content; + max-width: 60%; + padding: 5px 10px; + background: #FFFFFF; + margin: 2px 5px; + border: 1px solid black; + border-radius: 5px; + display: grid; + grid-template-columns: auto auto; + grid-template-areas: + 'text timestamp'; +} + +.chat-message__text { + grid-area: text; +} + +.chat-message__time { + grid-area: timestamp; + font-size: 0.8em; + align-self: flex-end; + margin: 0 10px; +} + +.chat-message--sent { + margin-left: auto; +} \ No newline at end of file diff --git a/webchat/components/chat/message/chat-message.html b/webchat/components/chat/message/chat-message.html new file mode 100644 index 000000000..9bec975f3 --- /dev/null +++ b/webchat/components/chat/message/chat-message.html @@ -0,0 +1,4 @@ +
+ {{ chatMessageCtrl.messageObj.msg }} + {{ chatMessageCtrl.formattedTimestamp }} +
\ No newline at end of file From 52cbc129f350aeb46a5e248a2301607c8f24ec35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Esp=C3=ADndula?= Date: Fri, 1 Mar 2019 13:15:28 -0300 Subject: [PATCH 02/45] Add chat-buttons component --- .../chat/buttons/chat-buttons.component.js | 18 ++++++++++++++++++ .../components/chat/buttons/chat-buttons.css | 0 .../components/chat/buttons/chat-buttons.html | 1 + 3 files changed, 19 insertions(+) create mode 100644 webchat/components/chat/buttons/chat-buttons.component.js create mode 100644 webchat/components/chat/buttons/chat-buttons.css create mode 100644 webchat/components/chat/buttons/chat-buttons.html diff --git a/webchat/components/chat/buttons/chat-buttons.component.js b/webchat/components/chat/buttons/chat-buttons.component.js new file mode 100644 index 000000000..08bf5d7ea --- /dev/null +++ b/webchat/components/chat/buttons/chat-buttons.component.js @@ -0,0 +1,18 @@ +(function () { + 'use strict'; + + angular.module("webchat").component("chatButtons", { + templateUrl: "app/components/chat/buttons/chat-buttons.html", + controller: chatButtonsController, + controllerAs: "chatButtonsCtrl", + bindings: { + callFunc: "<", + }, + }); + + function chatButtonsController() { + const chatButtonsCtrl = this; + + } + +})(); \ No newline at end of file diff --git a/webchat/components/chat/buttons/chat-buttons.css b/webchat/components/chat/buttons/chat-buttons.css new file mode 100644 index 000000000..e69de29bb diff --git a/webchat/components/chat/buttons/chat-buttons.html b/webchat/components/chat/buttons/chat-buttons.html new file mode 100644 index 000000000..7240fd019 --- /dev/null +++ b/webchat/components/chat/buttons/chat-buttons.html @@ -0,0 +1 @@ + \ No newline at end of file From 22689ecc85ddba8973efce18b5a501114dd4202f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Esp=C3=ADndula?= Date: Fri, 1 Mar 2019 13:15:58 -0300 Subject: [PATCH 03/45] Add chat-input component --- .../chat/input/chat-input.component.js | 39 +++++++++++++++++++ webchat/components/chat/input/chat-input.css | 22 +++++++++++ webchat/components/chat/input/chat-input.html | 14 +++++++ 3 files changed, 75 insertions(+) create mode 100644 webchat/components/chat/input/chat-input.component.js create mode 100644 webchat/components/chat/input/chat-input.css create mode 100644 webchat/components/chat/input/chat-input.html diff --git a/webchat/components/chat/input/chat-input.component.js b/webchat/components/chat/input/chat-input.component.js new file mode 100644 index 000000000..768511a44 --- /dev/null +++ b/webchat/components/chat/input/chat-input.component.js @@ -0,0 +1,39 @@ +(function () { + 'use strict'; + + angular.module("webchat").component("chatInput", { + templateUrl: "app/components/chat/input/chat-input.html", + controller: chatInputController, + controllerAs: "chatInputCtrl", + bindings: { + sendMessageFunc: "<", + state: "<" + }, + }); + + function chatInputController() { + const chatInputCtrl = this; + + chatInputCtrl.getState = () => chatInputCtrl.state; + + chatInputCtrl.getStateStyle = () => { + const state = chatInputCtrl.getState(); + if (_.includes(['connected', 'complete'], state)) { + return 'lawngreen'; + } else if (_.includes(['failed', 'disconnected', 'closed'], state)) { + return 'red'; + } else if (_.includes(['new', 'checking'], state)) { + return 'goldenrod'; + } + return 'lightgray'; + }; + + chatInputCtrl.sendMessage = () => { + if (chatInputCtrl.msg) { + chatInputCtrl.sendMessageFunc(chatInputCtrl.msg); + chatInputCtrl.msg = ''; + } + }; + } + +})(); \ No newline at end of file diff --git a/webchat/components/chat/input/chat-input.css b/webchat/components/chat/input/chat-input.css new file mode 100644 index 000000000..1f9764359 --- /dev/null +++ b/webchat/components/chat/input/chat-input.css @@ -0,0 +1,22 @@ +.chat-input__container { + height: 100%; + width: 100%; + display: flex; + align-content: center; + align-items: center; +} + +.chat-input__connection-state { + margin-left: 10px; +} + +.chat-input__text { + width: 100%; + height: 70%; + margin: 0 0 0 10px; + background: #FFFFFF; +} + +.chat-input__text:disabled { + background: #dddddd; +} \ No newline at end of file diff --git a/webchat/components/chat/input/chat-input.html b/webchat/components/chat/input/chat-input.html new file mode 100644 index 000000000..d62da383d --- /dev/null +++ b/webchat/components/chat/input/chat-input.html @@ -0,0 +1,14 @@ +
+

+ + + +
From f3e940cf021b2870465fe7c9f0eb414a675cec94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Esp=C3=ADndula?= Date: Fri, 1 Mar 2019 13:16:25 -0300 Subject: [PATCH 04/45] Add chat-body component --- .../components/chat/body/chat-body.component.js | 17 +++++++++++++++++ webchat/components/chat/body/chat-body.css | 8 ++++++++ webchat/components/chat/body/chat-body.html | 7 +++++++ 3 files changed, 32 insertions(+) create mode 100644 webchat/components/chat/body/chat-body.component.js create mode 100644 webchat/components/chat/body/chat-body.css create mode 100644 webchat/components/chat/body/chat-body.html diff --git a/webchat/components/chat/body/chat-body.component.js b/webchat/components/chat/body/chat-body.component.js new file mode 100644 index 000000000..cc3c4f4bc --- /dev/null +++ b/webchat/components/chat/body/chat-body.component.js @@ -0,0 +1,17 @@ +(function () { + 'use strict'; + + angular.module("webchat").component("chatBody", { + templateUrl: "app/components/chat/body/chat-body.html", + controller: chatBodyController, + controllerAs: "chatBodyCtrl", + bindings: { + messages: '<', + }, + }); + + function chatBodyController() { + const chatBodyCtrl = this; + } + +})(); \ No newline at end of file diff --git a/webchat/components/chat/body/chat-body.css b/webchat/components/chat/body/chat-body.css new file mode 100644 index 000000000..161d82c13 --- /dev/null +++ b/webchat/components/chat/body/chat-body.css @@ -0,0 +1,8 @@ +.chat-body__container { + height: 100%; + max-height: 100%; + display: flex; + flex-direction: column; + overflow-y: scroll; + justify-content: flex-end; +} diff --git a/webchat/components/chat/body/chat-body.html b/webchat/components/chat/body/chat-body.html new file mode 100644 index 000000000..48bd48557 --- /dev/null +++ b/webchat/components/chat/body/chat-body.html @@ -0,0 +1,7 @@ +
+ + +
\ No newline at end of file From 33470526108c8f4d4032aafcd98f139bd8c8da77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Esp=C3=ADndula?= Date: Fri, 1 Mar 2019 13:16:40 -0300 Subject: [PATCH 05/45] Add chat-header component --- .../chat/header/chat-header.component.js | 19 +++++++++++++++++++ .../components/chat/header/chat-header.css | 17 +++++++++++++++++ .../components/chat/header/chat-header.html | 8 ++++++++ 3 files changed, 44 insertions(+) create mode 100644 webchat/components/chat/header/chat-header.component.js create mode 100644 webchat/components/chat/header/chat-header.css create mode 100644 webchat/components/chat/header/chat-header.html diff --git a/webchat/components/chat/header/chat-header.component.js b/webchat/components/chat/header/chat-header.component.js new file mode 100644 index 000000000..f8949a503 --- /dev/null +++ b/webchat/components/chat/header/chat-header.component.js @@ -0,0 +1,19 @@ +(function () { + 'use strict'; + + angular.module("webchat").component("chatHeader", { + templateUrl: "app/components/chat/header/chat-header.html", + controller: chatHeaderController, + controllerAs: "chatHeaderCtrl", + bindings: { + user: "<", + chat: "<", + callFunc: "<", + }, + }); + + function chatHeaderController() { + const chatHeaderCtrl = this; + } + +})(); \ No newline at end of file diff --git a/webchat/components/chat/header/chat-header.css b/webchat/components/chat/header/chat-header.css new file mode 100644 index 000000000..b5f28f0bd --- /dev/null +++ b/webchat/components/chat/header/chat-header.css @@ -0,0 +1,17 @@ +.chat-header__container { + display: grid; + grid-template-columns: auto max-content; + grid-template-areas: + 'current-user video-buttons'; + align-items: center; +} + +.chat-header__current-user { + grid-area: current-user; + margin: 0 10px; + color: #EEEEEE; +} + +.chat-header__buttons { + grid-area: video-buttons; +} \ No newline at end of file diff --git a/webchat/components/chat/header/chat-header.html b/webchat/components/chat/header/chat-header.html new file mode 100644 index 000000000..c357f885f --- /dev/null +++ b/webchat/components/chat/header/chat-header.html @@ -0,0 +1,8 @@ +
+ + +
From bd755364f3adb0f7aa333c852e676e1c0a893dbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Esp=C3=ADndula?= Date: Fri, 1 Mar 2019 13:16:55 -0300 Subject: [PATCH 06/45] Add ecis-chat-component --- .../components/chat/ecis-chat.component.js | 28 +++++++++++++++++++ webchat/components/chat/ecis-chat.css | 25 +++++++++++++++++ webchat/components/chat/ecis-chat.html | 16 +++++++++++ 3 files changed, 69 insertions(+) create mode 100644 webchat/components/chat/ecis-chat.component.js create mode 100644 webchat/components/chat/ecis-chat.css create mode 100644 webchat/components/chat/ecis-chat.html diff --git a/webchat/components/chat/ecis-chat.component.js b/webchat/components/chat/ecis-chat.component.js new file mode 100644 index 000000000..a80eb9cab --- /dev/null +++ b/webchat/components/chat/ecis-chat.component.js @@ -0,0 +1,28 @@ +(function () { + 'use strict'; + + angular.module("webchat").component("ecisChat", { + templateUrl: "app/components/chat/ecis-chat.html", + controller: ecisChatController, + controllerAs: "ecisChatCtrl", + bindings: { + chat: '<', + user: '<', + callFunc: '<', + state: '<', + }, + }); + + function ecisChatController() { + const ecisChatCtrl = this; + + ecisChatCtrl.sendMessage = (msg) => { + ecisChatCtrl.chat.sendMessage(msg); + }; + + ecisChatCtrl.call = () => { + ecisChatCtrl.callFunc(ecisChatCtrl.user); + }; + } + +})(); \ No newline at end of file diff --git a/webchat/components/chat/ecis-chat.css b/webchat/components/chat/ecis-chat.css new file mode 100644 index 000000000..ee8a080f5 --- /dev/null +++ b/webchat/components/chat/ecis-chat.css @@ -0,0 +1,25 @@ +.chat__container { + height: 100%; + background: #EEEEEE; + display: grid; + grid-template-rows: 64px calc(100% - 64px - 48px) 48px; + grid-template-areas: + 'header' + 'body' + 'input'; +} + +.chat__header { + grid-area: header; + background: #009688; +} + +.chat__body { + grid-area: body; + background: #E5DDD3; +} + +.chat__input { + grid-area: input; + background: #009688; +} \ No newline at end of file diff --git a/webchat/components/chat/ecis-chat.html b/webchat/components/chat/ecis-chat.html new file mode 100644 index 000000000..b46d04748 --- /dev/null +++ b/webchat/components/chat/ecis-chat.html @@ -0,0 +1,16 @@ +
+ + + + + + +
From bc43450001a952d8e0167d9965de51d9797bd555 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Esp=C3=ADndula?= Date: Fri, 1 Mar 2019 13:17:38 -0300 Subject: [PATCH 07/45] Add chat components imports --- webchat/index.html | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/webchat/index.html b/webchat/index.html index 5d91c4f0e..5308c018d 100644 --- a/webchat/index.html +++ b/webchat/index.html @@ -58,6 +58,14 @@ + + + + + + + + @@ -125,6 +133,13 @@ + + + + + + + From ffd092c6039d52b5205d0a117bc713c411c6545f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Esp=C3=ADndula?= Date: Fri, 1 Mar 2019 13:18:27 -0300 Subject: [PATCH 08/45] Add chat component implementation --- .../toggle-button/toggle-button.component.js | 2 - webchat/home/home.html | 10 ++- webchat/home/homeController.js | 69 ++++++++++++++++++- 3 files changed, 75 insertions(+), 6 deletions(-) diff --git a/webchat/components/toggle-button/toggle-button.component.js b/webchat/components/toggle-button/toggle-button.component.js index c3575f400..94c0874d4 100644 --- a/webchat/components/toggle-button/toggle-button.component.js +++ b/webchat/components/toggle-button/toggle-button.component.js @@ -36,14 +36,12 @@ const toggleButtonCtrl = this; toggleButtonCtrl.$onInit = () => { - console.log(toggleButtonCtrl); _.defaults(toggleButtonCtrl, { active: true, iconColorOn: "#EEE", iconColorOff: "#EEE", action: () => {} }); - console.log(toggleButtonCtrl); }; toggleButtonCtrl.toggle = () => { diff --git a/webchat/home/home.html b/webchat/home/home.html index 2f19105a3..c4e19a178 100644 --- a/webchat/home/home.html +++ b/webchat/home/home.html @@ -1,3 +1,11 @@
- + + +
diff --git a/webchat/home/homeController.js b/webchat/home/homeController.js index fa8beabbb..b69a562bf 100644 --- a/webchat/home/homeController.js +++ b/webchat/home/homeController.js @@ -3,11 +3,74 @@ const webchat = angular.module('webchat'); - webchat.controller('HomeController', ['WebchatService', function HomeController (WebchatService) { + webchat.controller('HomeController', ['UserService', 'AuthService', 'WebchatService', 'MessageService', '$scope', function HomeController (UserService, AuthService, WebchatService, MessageService, $scope) { const homeCtrl = this; - homeCtrl.contacts = WebchatService.getContacts(); + homeCtrl.$onInit = () => { + homeCtrl.client = AuthService.chatClient; + homeCtrl.cachedUsers = {}; - }]); + homeCtrl.getUserList(homeCtrl.client.users); + homeCtrl.client.on('user-list-updated', homeCtrl.getUserList); + homeCtrl.client.on('call-requested', homeCtrl.promptCall); + homeCtrl.client.on('chat-created', homeCtrl.chatCreated); + }; + + homeCtrl.getUserList = (users) => { + const parsedUsers = []; + + if (users.forEach) { + users.forEach(userKey => { + if (userKey !== homeCtrl.client.id) { + if (!_.has(homeCtrl.cachedUsers, userKey)) { + UserService.getUser(userKey).then(user => { + homeCtrl.cachedUsers[userKey] = user; + parsedUsers.push(homeCtrl.cachedUsers[userKey]); + }); + } else { + parsedUsers.push(homeCtrl.cachedUsers[userKey]); + } + } + }); + } + homeCtrl.contacts = parsedUsers; + }; + + homeCtrl.openChat = (user) => { + homeCtrl.currentUser = user; + console.log(homeCtrl.client.chats[user.key]); + }; + homeCtrl.chatCreated = (e) => { + UserService.getUser(e.id).then(user => { + homeCtrl.currentChat = e.chat; + homeCtrl.currentUser = user; + homeCtrl.currentChat.on('ice-connection-changed', homeCtrl.stateChange); + homeCtrl.currentChat.on('msg-list-updated', list => { + $scope.$apply(); + }); + }); + }; + + homeCtrl.stateChange = (state) => { + homeCtrl.state = state; + console.log(state); + $scope.$apply(); + }; + + homeCtrl.call = (user) => { + homeCtrl.client.requestCall(user.key); + homeCtrl.openChat(user); + }; + + homeCtrl.promptCall = (id) => { + UserService.getUser(id).then(user => { + MessageService.showConfirmationDialog({}, "Ligação recebida", `Ligação de ${user.name}. Aceitar?`).then(answer => { + if (answer) { + homeCtrl.client.acceptCall(id); + } + }); + }); + }; + }]); })(); From 52b38e8ce7d517df2a2d5b5f9b21d08362df341b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Esp=C3=ADndula?= Date: Fri, 1 Mar 2019 13:21:20 -0300 Subject: [PATCH 09/45] Fix ecis-header user description to receive email --- webchat/components/header/ecis-header.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webchat/components/header/ecis-header.html b/webchat/components/header/ecis-header.html index f5d410fb5..a1eb40357 100644 --- a/webchat/components/header/ecis-header.html +++ b/webchat/components/header/ecis-header.html @@ -11,7 +11,7 @@ class="ecis-header__current-user" name="{{headerCtrl.user.name}}" avatar="{{headerCtrl.user.photo_url}}" - text="{{headerCtrl.user.current_institution.name}}"> + text="{{headerCtrl.user.email[0]}}"> Date: Thu, 7 Mar 2019 15:09:30 -0300 Subject: [PATCH 10/45] Remove unused WebchatService --- webchat/services/WebchatService.js | 11 ----------- 1 file changed, 11 deletions(-) delete mode 100644 webchat/services/WebchatService.js diff --git a/webchat/services/WebchatService.js b/webchat/services/WebchatService.js deleted file mode 100644 index 6977c4a15..000000000 --- a/webchat/services/WebchatService.js +++ /dev/null @@ -1,11 +0,0 @@ -(function () { - 'use strict'; - - angular.module("webchat").service('WebchatService', function WebchatService() { - const WebchatService = this; - - WebchatService.getContacts = () => []; - - - }); -})(); \ No newline at end of file From 74d7f951fe871431e08365d805f69acca9c00b89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Esp=C3=ADndula?= Date: Fri, 8 Mar 2019 10:12:23 -0300 Subject: [PATCH 11/45] Remove WebchatService imports --- webchat/home/homeController.js | 2 +- webchat/index.html | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/webchat/home/homeController.js b/webchat/home/homeController.js index b69a562bf..c659a827f 100644 --- a/webchat/home/homeController.js +++ b/webchat/home/homeController.js @@ -3,7 +3,7 @@ const webchat = angular.module('webchat'); - webchat.controller('HomeController', ['UserService', 'AuthService', 'WebchatService', 'MessageService', '$scope', function HomeController (UserService, AuthService, WebchatService, MessageService, $scope) { + webchat.controller('HomeController', ['UserService', 'AuthService', 'MessageService', '$scope', function HomeController (UserService, AuthService, MessageService, $scope) { const homeCtrl = this; homeCtrl.$onInit = () => { diff --git a/webchat/index.html b/webchat/index.html index 5308c018d..4718ee2a2 100644 --- a/webchat/index.html +++ b/webchat/index.html @@ -115,7 +115,6 @@ - From c64ab076a2bf46c8283935c7993f17cc5b090677 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Esp=C3=ADndula?= Date: Fri, 8 Mar 2019 10:29:15 -0300 Subject: [PATCH 12/45] Remove all console.log calls --- webchat/home/homeController.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/webchat/home/homeController.js b/webchat/home/homeController.js index c659a827f..c2effcf76 100644 --- a/webchat/home/homeController.js +++ b/webchat/home/homeController.js @@ -38,7 +38,6 @@ homeCtrl.openChat = (user) => { homeCtrl.currentUser = user; - console.log(homeCtrl.client.chats[user.key]); }; homeCtrl.chatCreated = (e) => { @@ -54,7 +53,6 @@ homeCtrl.stateChange = (state) => { homeCtrl.state = state; - console.log(state); $scope.$apply(); }; From 0db69b1363b69868a048cf6741aa9af60e56c86e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Esp=C3=ADndula?= Date: Fri, 8 Mar 2019 10:29:39 -0300 Subject: [PATCH 13/45] Replace default forEach for lodash forEach --- webchat/home/homeController.js | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/webchat/home/homeController.js b/webchat/home/homeController.js index c2effcf76..4de3f6d31 100644 --- a/webchat/home/homeController.js +++ b/webchat/home/homeController.js @@ -19,20 +19,18 @@ homeCtrl.getUserList = (users) => { const parsedUsers = []; - if (users.forEach) { - users.forEach(userKey => { - if (userKey !== homeCtrl.client.id) { - if (!_.has(homeCtrl.cachedUsers, userKey)) { - UserService.getUser(userKey).then(user => { - homeCtrl.cachedUsers[userKey] = user; - parsedUsers.push(homeCtrl.cachedUsers[userKey]); - }); - } else { + _.forEach(users, userKey => { + if (userKey !== homeCtrl.client.id) { + if (!_.has(homeCtrl.cachedUsers, userKey)) { + UserService.getUser(userKey).then(user => { + homeCtrl.cachedUsers[userKey] = user; parsedUsers.push(homeCtrl.cachedUsers[userKey]); - } + }); + } else { + parsedUsers.push(homeCtrl.cachedUsers[userKey]); } - }); - } + } + }); homeCtrl.contacts = parsedUsers; }; From 29424e27d516a036381a75992b1b20a7f557c2fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Esp=C3=ADndula?= Date: Fri, 8 Mar 2019 10:34:10 -0300 Subject: [PATCH 14/45] Replace calc call for 1fr in chat__container grid definition --- webchat/components/chat/ecis-chat.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webchat/components/chat/ecis-chat.css b/webchat/components/chat/ecis-chat.css index ee8a080f5..0cefd60d2 100644 --- a/webchat/components/chat/ecis-chat.css +++ b/webchat/components/chat/ecis-chat.css @@ -2,7 +2,7 @@ height: 100%; background: #EEEEEE; display: grid; - grid-template-rows: 64px calc(100% - 64px - 48px) 48px; + grid-template-rows: max-content 1fr max-content; grid-template-areas: 'header' 'body' From fe4e080530eb76a77a316b9e5cc92b5fbc53fce5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Esp=C3=ADndula?= Date: Fri, 8 Mar 2019 10:38:18 -0300 Subject: [PATCH 15/45] Add min-height to chat-input__text class --- webchat/components/chat/input/chat-input.css | 1 + 1 file changed, 1 insertion(+) diff --git a/webchat/components/chat/input/chat-input.css b/webchat/components/chat/input/chat-input.css index 1f9764359..040d61af9 100644 --- a/webchat/components/chat/input/chat-input.css +++ b/webchat/components/chat/input/chat-input.css @@ -15,6 +15,7 @@ height: 70%; margin: 0 0 0 10px; background: #FFFFFF; + min-height: 36px; } .chat-input__text:disabled { From ec624d02d9c722890c1acd04ff80d564ae334eab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Esp=C3=ADndula?= Date: Fri, 8 Mar 2019 10:46:07 -0300 Subject: [PATCH 16/45] Add sendMessage on enter pressed --- webchat/components/chat/input/chat-input.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/webchat/components/chat/input/chat-input.html b/webchat/components/chat/input/chat-input.html index d62da383d..e6f7877e7 100644 --- a/webchat/components/chat/input/chat-input.html +++ b/webchat/components/chat/input/chat-input.html @@ -4,7 +4,8 @@ class="chat-input__connection-state">⬤

+ ng-model="chatInputCtrl.msg" + ng-keydown="$event.keyCode === 13 && chatInputCtrl.sendMessage()"/> Date: Fri, 8 Mar 2019 10:57:42 -0300 Subject: [PATCH 17/45] Add autoclose sidenav when a chat is open and is on mobile --- webchat/home/homeController.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/webchat/home/homeController.js b/webchat/home/homeController.js index 4de3f6d31..43a3d92d5 100644 --- a/webchat/home/homeController.js +++ b/webchat/home/homeController.js @@ -3,7 +3,8 @@ const webchat = angular.module('webchat'); - webchat.controller('HomeController', ['UserService', 'AuthService', 'MessageService', '$scope', function HomeController (UserService, AuthService, MessageService, $scope) { + webchat.controller('HomeController', ['UserService', 'AuthService', 'MessageService', '$scope', 'NavbarManagementService', + function HomeController (UserService, AuthService, MessageService, $scope, NavbarManagementService) { const homeCtrl = this; homeCtrl.$onInit = () => { @@ -36,6 +37,9 @@ homeCtrl.openChat = (user) => { homeCtrl.currentUser = user; + if (Utils.isMobileScreen()) { + NavbarManagementService.toggleSidenav('left'); + } }; homeCtrl.chatCreated = (e) => { From 5f288d380bfef9686b64034bb0c7c0a8cfef07e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Esp=C3=ADndula?= Date: Fri, 8 Mar 2019 12:33:55 -0300 Subject: [PATCH 18/45] Add disabled input when state is not connected --- webchat/components/chat/input/chat-input.component.js | 5 +++++ webchat/components/chat/input/chat-input.html | 6 ++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/webchat/components/chat/input/chat-input.component.js b/webchat/components/chat/input/chat-input.component.js index 768511a44..c7e90699a 100644 --- a/webchat/components/chat/input/chat-input.component.js +++ b/webchat/components/chat/input/chat-input.component.js @@ -34,6 +34,11 @@ chatInputCtrl.msg = ''; } }; + + chatInputCtrl.inputDisabled = () => { + return !_.includes(['connected', 'complete'], chatInputCtrl.state); + }; + } })(); \ No newline at end of file diff --git a/webchat/components/chat/input/chat-input.html b/webchat/components/chat/input/chat-input.html index e6f7877e7..45660ddb2 100644 --- a/webchat/components/chat/input/chat-input.html +++ b/webchat/components/chat/input/chat-input.html @@ -5,11 +5,13 @@ + ng-keydown="$event.keyCode === 13 && chatInputCtrl.sendMessage()" + ng-disabled="chatInputCtrl.inputDisabled()"/> + action="chatInputCtrl.sendMessage" + disabled="chatInputCtrl.inputDisabled()"> From 6c9f81344d45793fccf483c2c64eb00f52065f73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Esp=C3=ADndula?= Date: Tue, 12 Mar 2019 20:31:06 -0300 Subject: [PATCH 19/45] Fix typo on state definition --- webchat/components/chat/input/chat-input.component.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/webchat/components/chat/input/chat-input.component.js b/webchat/components/chat/input/chat-input.component.js index c7e90699a..b365ac7b8 100644 --- a/webchat/components/chat/input/chat-input.component.js +++ b/webchat/components/chat/input/chat-input.component.js @@ -18,7 +18,7 @@ chatInputCtrl.getStateStyle = () => { const state = chatInputCtrl.getState(); - if (_.includes(['connected', 'complete'], state)) { + if (_.includes(['connected', 'completed'], state)) { return 'lawngreen'; } else if (_.includes(['failed', 'disconnected', 'closed'], state)) { return 'red'; @@ -36,7 +36,7 @@ }; chatInputCtrl.inputDisabled = () => { - return !_.includes(['connected', 'complete'], chatInputCtrl.state); + return !_.includes(['connected', 'completed'], chatInputCtrl.state); }; } From a2c75dada081cdf22777cf77eb67b7c48b4ccb1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Esp=C3=ADndula?= Date: Tue, 12 Mar 2019 20:31:34 -0300 Subject: [PATCH 20/45] Add structure to add video on webchat --- webchat/home/homeController.js | 57 +++++++++++++++++++++++++--------- 1 file changed, 42 insertions(+), 15 deletions(-) diff --git a/webchat/home/homeController.js b/webchat/home/homeController.js index 43a3d92d5..b4b1c08c7 100644 --- a/webchat/home/homeController.js +++ b/webchat/home/homeController.js @@ -43,13 +43,23 @@ }; homeCtrl.chatCreated = (e) => { - UserService.getUser(e.id).then(user => { - homeCtrl.currentChat = e.chat; - homeCtrl.currentUser = user; - homeCtrl.currentChat.on('ice-connection-changed', homeCtrl.stateChange); - homeCtrl.currentChat.on('msg-list-updated', list => { - $scope.$apply(); - }); + homeCtrl.currentUser = homeCtrl.getUser(e.id); + homeCtrl.currentChat = e.chat; + + const selfie = document.getElementById('video-selfie'); + selfie.srcObject = homeCtrl.currentChat.selfStream; + selfie.play().then().catch(e => console.log('cant play video: ', e)); + + homeCtrl.currentChat.on('ice-connection-changed', homeCtrl.stateChange); + homeCtrl.currentChat.on('msg-list-updated', list => { + $scope.$apply(); + }); + + homeCtrl.currentChat.on('track-received', ev => { + const el = document.getElementById('video-remote'); + el.srcObject = ev.streams[0]; + el.play().then().catch(e => console.log('cant play video: ', e)); + $scope.$apply(); }); }; @@ -59,18 +69,35 @@ }; homeCtrl.call = (user) => { - homeCtrl.client.requestCall(user.key); - homeCtrl.openChat(user); + getMedia().then(stream => { + homeCtrl.client.requestCall(user.key, stream); + }); }; homeCtrl.promptCall = (id) => { - UserService.getUser(id).then(user => { - MessageService.showConfirmationDialog({}, "Ligação recebida", `Ligação de ${user.name}. Aceitar?`).then(answer => { - if (answer) { - homeCtrl.client.acceptCall(id); - } - }); + const user = homeCtrl.getUser(id); + + MessageService.showConfirmationDialog({}, "Ligação recebida", `Ligação de ${user.name}. Aceitar?`).then(answer => { + if (answer) { + homeCtrl.openChat(user); + getMedia().then(stream => { + homeCtrl.client.acceptCall(id, stream); + }); + } + }); + }; + + function getMedia() { + return new Promise((resolve, reject) => { + let stream; + navigator.mediaDevices.getUserMedia({ video: true, audio: true }).then(s => { + stream = s; + }).catch(e => console.log(e)).finally(() => resolve(stream)); }); + } + + homeCtrl.getUser = (id) => { + return homeCtrl.cachedUsers[id]; }; }]); })(); From 08411a5cd52f058207b66da42bb910d422ea3592 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Esp=C3=ADndula?= Date: Wed, 13 Mar 2019 11:24:05 -0300 Subject: [PATCH 21/45] Modify toggle button to have a specific function to call when on and off --- .../toggle-button/toggle-button.component.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/webchat/components/toggle-button/toggle-button.component.js b/webchat/components/toggle-button/toggle-button.component.js index 94c0874d4..50ce06ece 100644 --- a/webchat/components/toggle-button/toggle-button.component.js +++ b/webchat/components/toggle-button/toggle-button.component.js @@ -28,7 +28,8 @@ iconOff: '@', iconColorOn: '@', iconColorOff: '@', - action: '<', + actionOn: '<', + actionOff: '<', }, }); @@ -40,13 +41,14 @@ active: true, iconColorOn: "#EEE", iconColorOff: "#EEE", - action: () => {} + actionOn: () => {}, + actionOff: () => {}, }); }; toggleButtonCtrl.toggle = () => { toggleButtonCtrl.active = !toggleButtonCtrl.active; - toggleButtonCtrl.action(); + toggleButtonCtrl.activeActionFunc(); }; Object.defineProperty(toggleButtonCtrl, 'activeIcon', { @@ -60,6 +62,12 @@ return toggleButtonCtrl.active ? toggleButtonCtrl.iconColorOn : toggleButtonCtrl.iconColorOff; }, }); + + Object.defineProperty(toggleButtonCtrl, 'activeActionFunc', { + get: () => { + return toggleButtonCtrl.active ? toggleButtonCtrl.actionOn : toggleButtonCtrl.actionOff; + }, + }); } })(); \ No newline at end of file From 5d509d170999eadc6cc93c38049bcafc52723bcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Esp=C3=ADndula?= Date: Wed, 13 Mar 2019 11:24:51 -0300 Subject: [PATCH 22/45] Add video and audio toggle buttons on chat --- webchat/components/chat/buttons/chat-buttons.html | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/webchat/components/chat/buttons/chat-buttons.html b/webchat/components/chat/buttons/chat-buttons.html index 7240fd019..8a5541348 100644 --- a/webchat/components/chat/buttons/chat-buttons.html +++ b/webchat/components/chat/buttons/chat-buttons.html @@ -1 +1,11 @@ - \ No newline at end of file + + + From 88e6ae0fec6a1418f179163c1f711ef0d5770a67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Esp=C3=ADndula?= Date: Wed, 13 Mar 2019 11:26:31 -0300 Subject: [PATCH 23/45] Fix navbar shown when accept call --- webchat/home/homeController.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webchat/home/homeController.js b/webchat/home/homeController.js index b4b1c08c7..6cd162341 100644 --- a/webchat/home/homeController.js +++ b/webchat/home/homeController.js @@ -79,7 +79,7 @@ MessageService.showConfirmationDialog({}, "Ligação recebida", `Ligação de ${user.name}. Aceitar?`).then(answer => { if (answer) { - homeCtrl.openChat(user); + homeCtrl.currentUser = user; getMedia().then(stream => { homeCtrl.client.acceptCall(id, stream); }); From 382be66cacad3d0395c5c73dc088b07539b61f13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Esp=C3=ADndula?= Date: Wed, 13 Mar 2019 11:28:40 -0300 Subject: [PATCH 24/45] Add video tags on chat body --- webchat/components/chat/body/chat-body.html | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/webchat/components/chat/body/chat-body.html b/webchat/components/chat/body/chat-body.html index 48bd48557..9d87fec32 100644 --- a/webchat/components/chat/body/chat-body.html +++ b/webchat/components/chat/body/chat-body.html @@ -4,4 +4,8 @@ ng-repeat="message in chatBodyCtrl.messages" message-obj="message"> - \ No newline at end of file + +
+ + +
From d5231988a77792691172d1b3a6fb2d637030c34f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Esp=C3=ADndula?= Date: Thu, 14 Mar 2019 09:10:55 -0300 Subject: [PATCH 25/45] Add videoStream passed as a binding to chat body --- .../chat/body/chat-body.component.js | 24 +++++++++++++++++++ .../components/chat/ecis-chat.component.js | 2 ++ webchat/components/chat/ecis-chat.html | 4 +++- webchat/home/home.html | 4 +++- webchat/home/homeController.js | 9 ++----- 5 files changed, 34 insertions(+), 9 deletions(-) diff --git a/webchat/components/chat/body/chat-body.component.js b/webchat/components/chat/body/chat-body.component.js index cc3c4f4bc..de7334f6d 100644 --- a/webchat/components/chat/body/chat-body.component.js +++ b/webchat/components/chat/body/chat-body.component.js @@ -7,11 +7,35 @@ controllerAs: "chatBodyCtrl", bindings: { messages: '<', + videoActive: '<', + selfieStream: '<', + remoteStream: '<', }, }); function chatBodyController() { const chatBodyCtrl = this; + + chatBodyCtrl.$onChanges = (changesObj) => { + updateSelfieVideo(changesObj); + updateRemoteVideo(changesObj); + }; + + const updateSelfieVideo = (changesObj) => { + if (_.has(changesObj, 'selfieStream.currentValue')) { + const selfieVideo = document.getElementById('video-selfie'); + selfieVideo.srcObject = chatBodyCtrl.selfieStream; + selfieVideo.play(); + } + }; + + const updateRemoteVideo = (changesObj) => { + if (_.has(changesObj, 'remoteStream.currentValue')) { + const remoteVideo = document.getElementById('video-remote'); + remoteVideo.srcObject = chatBodyCtrl.remoteStream; + remoteVideo.play(); + } + }; } })(); \ No newline at end of file diff --git a/webchat/components/chat/ecis-chat.component.js b/webchat/components/chat/ecis-chat.component.js index a80eb9cab..a9b7d0958 100644 --- a/webchat/components/chat/ecis-chat.component.js +++ b/webchat/components/chat/ecis-chat.component.js @@ -10,6 +10,8 @@ user: '<', callFunc: '<', state: '<', + selfieStream: '<', + remoteStream: '<', }, }); diff --git a/webchat/components/chat/ecis-chat.html b/webchat/components/chat/ecis-chat.html index b46d04748..1bcff6d70 100644 --- a/webchat/components/chat/ecis-chat.html +++ b/webchat/components/chat/ecis-chat.html @@ -6,7 +6,9 @@ + messages="ecisChatCtrl.chat.currentMessages" + selfie-stream="ecisChatCtrl.selfieStream" + remote-stream="ecisChatCtrl.remoteStream"> + state="homeCtrl.state" + selfie-stream="homeCtrl.selfieStream" + remote-stream="homeCtrl.remoteStream"> diff --git a/webchat/home/homeController.js b/webchat/home/homeController.js index 6cd162341..f683db4e8 100644 --- a/webchat/home/homeController.js +++ b/webchat/home/homeController.js @@ -46,9 +46,7 @@ homeCtrl.currentUser = homeCtrl.getUser(e.id); homeCtrl.currentChat = e.chat; - const selfie = document.getElementById('video-selfie'); - selfie.srcObject = homeCtrl.currentChat.selfStream; - selfie.play().then().catch(e => console.log('cant play video: ', e)); + homeCtrl.selfieStream = homeCtrl.currentChat.selfStream; homeCtrl.currentChat.on('ice-connection-changed', homeCtrl.stateChange); homeCtrl.currentChat.on('msg-list-updated', list => { @@ -56,10 +54,7 @@ }); homeCtrl.currentChat.on('track-received', ev => { - const el = document.getElementById('video-remote'); - el.srcObject = ev.streams[0]; - el.play().then().catch(e => console.log('cant play video: ', e)); - $scope.$apply(); + homeCtrl.remoteStream = ev.streams[0]; }); }; From 1a6a75882acf1985cde1d439d07cbd31cecdc056 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Esp=C3=ADndula?= Date: Thu, 14 Mar 2019 13:08:35 -0300 Subject: [PATCH 26/45] Add specific height an width to video --- webchat/components/chat/body/chat-body.css | 28 +++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/webchat/components/chat/body/chat-body.css b/webchat/components/chat/body/chat-body.css index 161d82c13..98db2bf5b 100644 --- a/webchat/components/chat/body/chat-body.css +++ b/webchat/components/chat/body/chat-body.css @@ -1,4 +1,9 @@ -.chat-body__container { +.chat-body { + height: 100%; + width: 100%; +} + +.chat-body__messages-container { height: 100%; max-height: 100%; display: flex; @@ -6,3 +11,24 @@ overflow-y: scroll; justify-content: flex-end; } + +.chat-body__video-container { + background-color: darkgray; + max-width: 100%; + max-height: 100%; + height: 100%; + width: 100%; +} + +#video-remote { + height: 100%; + width: 100%; + max-width: 100%; + max-height: 100%; +} + +#video-selfie { + display: none; + max-width: 100%; + max-height: 100%; +} \ No newline at end of file From 21fccf4f6f2917259a097f06c52c8137656d1afe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Esp=C3=ADndula?= Date: Thu, 14 Mar 2019 13:09:12 -0300 Subject: [PATCH 27/45] Add video toggle --- webchat/components/chat/body/chat-body.html | 4 ++-- .../chat/buttons/chat-buttons.component.js | 2 ++ webchat/components/chat/buttons/chat-buttons.html | 4 ++-- webchat/components/chat/ecis-chat.component.js | 11 +++++++++++ webchat/components/chat/ecis-chat.html | 5 ++++- .../components/chat/header/chat-header.component.js | 3 +++ webchat/components/chat/header/chat-header.html | 5 ++++- .../toggle-button/toggle-button.component.js | 2 +- webchat/home/homeController.js | 12 ++++++++---- 9 files changed, 37 insertions(+), 11 deletions(-) diff --git a/webchat/components/chat/body/chat-body.html b/webchat/components/chat/body/chat-body.html index 9d87fec32..b1a63ce13 100644 --- a/webchat/components/chat/body/chat-body.html +++ b/webchat/components/chat/body/chat-body.html @@ -1,11 +1,11 @@ -
+
-
+
diff --git a/webchat/components/chat/buttons/chat-buttons.component.js b/webchat/components/chat/buttons/chat-buttons.component.js index 08bf5d7ea..fa9bd908b 100644 --- a/webchat/components/chat/buttons/chat-buttons.component.js +++ b/webchat/components/chat/buttons/chat-buttons.component.js @@ -7,6 +7,8 @@ controllerAs: "chatButtonsCtrl", bindings: { callFunc: "<", + enableVideoFunc: "<", + disableVideoFunc: "<", }, }); diff --git a/webchat/components/chat/buttons/chat-buttons.html b/webchat/components/chat/buttons/chat-buttons.html index 8a5541348..4aef7cd55 100644 --- a/webchat/components/chat/buttons/chat-buttons.html +++ b/webchat/components/chat/buttons/chat-buttons.html @@ -1,8 +1,8 @@ + action-on="chatButtonsCtrl.enableVideoFunc" + action-off="chatButtonsCtrl.disableVideoFunc"> { ecisChatCtrl.callFunc(ecisChatCtrl.user); }; + + ecisChatCtrl.disableVideo = () => { + ecisChatCtrl.videoActive = false; + console.log('disabled'); + }; + + ecisChatCtrl.enableVideo = () => { + ecisChatCtrl.videoActive = true; + console.log('enabled'); + }; + } })(); \ No newline at end of file diff --git a/webchat/components/chat/ecis-chat.html b/webchat/components/chat/ecis-chat.html index 1bcff6d70..153b534ba 100644 --- a/webchat/components/chat/ecis-chat.html +++ b/webchat/components/chat/ecis-chat.html @@ -2,11 +2,14 @@ + call-func="ecisChatCtrl.call" + enable-video-func="ecisChatCtrl.enableVideo" + disable-video-func="ecisChatCtrl.disableVideo"> diff --git a/webchat/components/chat/header/chat-header.component.js b/webchat/components/chat/header/chat-header.component.js index f8949a503..32fe45182 100644 --- a/webchat/components/chat/header/chat-header.component.js +++ b/webchat/components/chat/header/chat-header.component.js @@ -9,11 +9,14 @@ user: "<", chat: "<", callFunc: "<", + enableVideoFunc: '<', + disableVideoFunc: '<', }, }); function chatHeaderController() { const chatHeaderCtrl = this; + } })(); \ No newline at end of file diff --git a/webchat/components/chat/header/chat-header.html b/webchat/components/chat/header/chat-header.html index c357f885f..1e11de890 100644 --- a/webchat/components/chat/header/chat-header.html +++ b/webchat/components/chat/header/chat-header.html @@ -4,5 +4,8 @@ avatar="{{ chatHeaderCtrl.user.photo_url }}" name="{{ chatHeaderCtrl.user.name }}" text="{{ chatHeaderCtrl.user.email[0] }}"> - +
diff --git a/webchat/components/toggle-button/toggle-button.component.js b/webchat/components/toggle-button/toggle-button.component.js index 50ce06ece..ce965cdc6 100644 --- a/webchat/components/toggle-button/toggle-button.component.js +++ b/webchat/components/toggle-button/toggle-button.component.js @@ -47,8 +47,8 @@ }; toggleButtonCtrl.toggle = () => { - toggleButtonCtrl.active = !toggleButtonCtrl.active; toggleButtonCtrl.activeActionFunc(); + toggleButtonCtrl.active = !toggleButtonCtrl.active; }; Object.defineProperty(toggleButtonCtrl, 'activeIcon', { diff --git a/webchat/home/homeController.js b/webchat/home/homeController.js index f683db4e8..f22e830ca 100644 --- a/webchat/home/homeController.js +++ b/webchat/home/homeController.js @@ -74,14 +74,18 @@ MessageService.showConfirmationDialog({}, "Ligação recebida", `Ligação de ${user.name}. Aceitar?`).then(answer => { if (answer) { - homeCtrl.currentUser = user; - getMedia().then(stream => { - homeCtrl.client.acceptCall(id, stream); - }); + homeCtrl.acceptCall(user, id); } }); }; + homeCtrl.acceptCall = (user, id) => { + homeCtrl.currentUser = user; + getMedia().then(stream => { + homeCtrl.client.acceptCall(id, stream); + }); + }; + function getMedia() { return new Promise((resolve, reject) => { let stream; From 4a41716a12e00949c51d3c45d343990fed1a1f10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Esp=C3=ADndula?= Date: Fri, 15 Mar 2019 13:28:56 -0300 Subject: [PATCH 28/45] Apply responsability to contain remote stream to chat --- .../components/chat/body/chat-body.component.js | 12 ++++++++---- webchat/components/chat/ecis-chat.component.js | 2 -- webchat/home/home.html | 4 ++-- webchat/home/homeController.js | 15 ++++----------- webchat/utils/chat.js | 8 +++++++- 5 files changed, 21 insertions(+), 20 deletions(-) diff --git a/webchat/components/chat/body/chat-body.component.js b/webchat/components/chat/body/chat-body.component.js index de7334f6d..fda116370 100644 --- a/webchat/components/chat/body/chat-body.component.js +++ b/webchat/components/chat/body/chat-body.component.js @@ -22,17 +22,21 @@ }; const updateSelfieVideo = (changesObj) => { - if (_.has(changesObj, 'selfieStream.currentValue')) { + const canUpdate = _.get(changesObj, 'selfieStream.currentValue.active', false); + + if (canUpdate) { const selfieVideo = document.getElementById('video-selfie'); - selfieVideo.srcObject = chatBodyCtrl.selfieStream; + selfieVideo.srcObject = changesObj.selfieStream.currentValue; selfieVideo.play(); } }; const updateRemoteVideo = (changesObj) => { - if (_.has(changesObj, 'remoteStream.currentValue')) { + const canUpdate = _.get(changesObj, 'remoteStream.currentValue.active', false); + + if (canUpdate) { const remoteVideo = document.getElementById('video-remote'); - remoteVideo.srcObject = chatBodyCtrl.remoteStream; + remoteVideo.srcObject = changesObj.remoteStream.currentValue; remoteVideo.play(); } }; diff --git a/webchat/components/chat/ecis-chat.component.js b/webchat/components/chat/ecis-chat.component.js index 6dfcb06da..19c137352 100644 --- a/webchat/components/chat/ecis-chat.component.js +++ b/webchat/components/chat/ecis-chat.component.js @@ -28,12 +28,10 @@ ecisChatCtrl.disableVideo = () => { ecisChatCtrl.videoActive = false; - console.log('disabled'); }; ecisChatCtrl.enableVideo = () => { ecisChatCtrl.videoActive = true; - console.log('enabled'); }; } diff --git a/webchat/home/home.html b/webchat/home/home.html index b1a40114f..8a81faeb5 100644 --- a/webchat/home/home.html +++ b/webchat/home/home.html @@ -7,7 +7,7 @@ user="homeCtrl.currentUser" call-func="homeCtrl.call" state="homeCtrl.state" - selfie-stream="homeCtrl.selfieStream" - remote-stream="homeCtrl.remoteStream"> + selfie-stream="homeCtrl.currentChat.selfieStream" + remote-stream="homeCtrl.currentChat.remoteStream">
diff --git a/webchat/home/homeController.js b/webchat/home/homeController.js index f22e830ca..ff23092fe 100644 --- a/webchat/home/homeController.js +++ b/webchat/home/homeController.js @@ -37,25 +37,18 @@ homeCtrl.openChat = (user) => { homeCtrl.currentUser = user; - if (Utils.isMobileScreen()) { - NavbarManagementService.toggleSidenav('left'); - } + homeCtrl.currentChat = homeCtrl.client.chats[user.key] || {}; + }; homeCtrl.chatCreated = (e) => { - homeCtrl.currentUser = homeCtrl.getUser(e.id); - homeCtrl.currentChat = e.chat; - - homeCtrl.selfieStream = homeCtrl.currentChat.selfStream; + homeCtrl.openChat(homeCtrl.getUser(e.id)); homeCtrl.currentChat.on('ice-connection-changed', homeCtrl.stateChange); homeCtrl.currentChat.on('msg-list-updated', list => { $scope.$apply(); }); - homeCtrl.currentChat.on('track-received', ev => { - homeCtrl.remoteStream = ev.streams[0]; - }); }; homeCtrl.stateChange = (state) => { @@ -80,7 +73,7 @@ }; homeCtrl.acceptCall = (user, id) => { - homeCtrl.currentUser = user; + homeCtrl.openChat(user); getMedia().then(stream => { homeCtrl.client.acceptCall(id, stream); }); diff --git a/webchat/utils/chat.js b/webchat/utils/chat.js index ffd48d558..cc210f8d0 100644 --- a/webchat/utils/chat.js +++ b/webchat/utils/chat.js @@ -25,10 +25,15 @@ this.rpc.onicecandidate = e => this.emit('ice-candidate-discovered', e); this.sendChannel = this.rpc.createDataChannel('sendChannel'); this.rpc.ondatachannel = this.handleDataChannel.bind(this); - this.rpc.ontrack = e => this.emit('track-received', e); + this.rpc.ontrack = this.handleTrack.bind(this); this.rpc.oniceconnectionstatechange = this.handleIceConnectionState.bind(this); this.rpc.onsignalingstatechange = this.handleState.bind(this); this._currentMessages = []; + this._remoteStream = {}; + } + + get remoteStream() { + return this._remoteStream; } get selfStream() { @@ -171,6 +176,7 @@ * @param {Event} e - event which contains the stream object and its tracks. */ handleTrack(e) { + this._remoteStream = e.streams[0]; this.emit('track-received', e); } From 6fa2345fc5c7af5c963291254e276e4ca449f74e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Esp=C3=ADndula?= Date: Fri, 15 Mar 2019 15:06:56 -0300 Subject: [PATCH 29/45] Fix dialog confirm action not triggering --- webchat/home/homeController.js | 8 +++--- webchat/index.html | 2 +- webchat/styles/custom/custom.css | 5 ++++ webchat/utils/confirm_dialog.css | 29 ++++++++++++++++++++++ webchat/utils/confirm_dialog.html | 15 +++++++++++ webchat/utils/messageService.js | 41 +++++++++++++++++++++++-------- 6 files changed, 85 insertions(+), 15 deletions(-) create mode 100644 webchat/utils/confirm_dialog.css create mode 100644 webchat/utils/confirm_dialog.html diff --git a/webchat/home/homeController.js b/webchat/home/homeController.js index ff23092fe..7bcff2c8c 100644 --- a/webchat/home/homeController.js +++ b/webchat/home/homeController.js @@ -65,10 +65,10 @@ homeCtrl.promptCall = (id) => { const user = homeCtrl.getUser(id); - MessageService.showConfirmationDialog({}, "Ligação recebida", `Ligação de ${user.name}. Aceitar?`).then(answer => { - if (answer) { - homeCtrl.acceptCall(user, id); - } + MessageService.showConfirmationDialog({}, { + title: user.name, + subtitle: "Está te ligando você deseja atender essa ligação?", + confirmAction: () => homeCtrl.acceptCall(user, id), }); }; diff --git a/webchat/index.html b/webchat/index.html index 4718ee2a2..26d4db6f5 100644 --- a/webchat/index.html +++ b/webchat/index.html @@ -66,7 +66,7 @@ - + diff --git a/webchat/styles/custom/custom.css b/webchat/styles/custom/custom.css index 4da3533e0..3978a6af5 100644 --- a/webchat/styles/custom/custom.css +++ b/webchat/styles/custom/custom.css @@ -5,4 +5,9 @@ background-color: #EEEEEE; border: none; outline: none; +} + +.custom-title { + font-size: 20px; + font-weight: 500; } \ No newline at end of file diff --git a/webchat/utils/confirm_dialog.css b/webchat/utils/confirm_dialog.css new file mode 100644 index 000000000..0ced2c801 --- /dev/null +++ b/webchat/utils/confirm_dialog.css @@ -0,0 +1,29 @@ +.dialog__container { + display: grid; + grid-template-areas: + "title" + "subtitle" + "buttons"; + padding: 1.5em 2em 0; +} + +.dialog__title { + grid-area: title; + margin: 0; +} + +.dialog__subtitle { + grid-area: subtitle; + padding: 0.5em 0; +} + +.dialog__buttons { + grid-area: buttons; + display: flex; + flex-direction: row; + justify-content: flex-end; +} + +.dialog__button { + margin: 1em 0; +} \ No newline at end of file diff --git a/webchat/utils/confirm_dialog.html b/webchat/utils/confirm_dialog.html new file mode 100644 index 000000000..840422bff --- /dev/null +++ b/webchat/utils/confirm_dialog.html @@ -0,0 +1,15 @@ + + + {{ dialogCtrl.title }} + {{ dialogCtrl.subtitle }} + +
+ + {{ dialogCtrl.cancelText }} + + + {{ dialogCtrl.confirmText }} + +
+
+
diff --git a/webchat/utils/messageService.js b/webchat/utils/messageService.js index 001b710e2..ab0df3f6a 100644 --- a/webchat/utils/messageService.js +++ b/webchat/utils/messageService.js @@ -28,19 +28,40 @@ return (message && msg[message.code]) || msg[message] || message; } - service.showConfirmationDialog = function showConfirmationDialog(event, title, textContent) { - const confirm = $mdDialog.confirm() - .clickOutsideToClose(true) - .title(title) - .textContent(textContent) - .ariaLabel(title) - .targetEvent(event) - .ok('Ok') - .cancel('Cancelar'); + service.showConfirmationDialog = function showConfirmationDialog(event, params) { + const dialog = { + templateUrl: "app/utils/confirm_dialog.html", + controller: dialogController, + controllerAs: 'dialogCtrl', + parent: angular.element(document.body), + targetEvent: event, + clickOutsideToClose:true, + locals: params, + }; - return $mdDialog.show(confirm); + return $mdDialog.show(dialog); }; + function dialogController (locals) { + const dialogCtrl = this; + + dialogCtrl.$onInit = () => { + _.assign(dialogCtrl, locals); + _.defaults(dialogCtrl, { + confirmAction: () => {}, + cancelText: "Cancelar", + confirmText: "Confirmar", + }); + }; + + dialogCtrl.cancelDialog = $mdDialog.cancel; + + dialogCtrl.confirmDialog = () => { + dialogCtrl.confirmAction(); + $mdDialog.hide(); + }; + } + service.showMessageDialog = function (event, message) { function MessageController ($mdDialog) { From 2a730505cb5be35b59774ff56e948ad641543ee0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Esp=C3=ADndula?= Date: Fri, 15 Mar 2019 16:35:34 -0300 Subject: [PATCH 30/45] Fix message shown when there is no contacts online --- webchat/components/contacts/list/contacts-list.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webchat/components/contacts/list/contacts-list.html b/webchat/components/contacts/list/contacts-list.html index 5355da2eb..07426aba9 100644 --- a/webchat/components/contacts/list/contacts-list.html +++ b/webchat/components/contacts/list/contacts-list.html @@ -12,5 +12,5 @@ -

Não há contatos online no momento.

+

Não há contatos online no momento.

\ No newline at end of file From 59f6457e5956aefce92952b7099bc7c748ffb5bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Esp=C3=ADndula?= Date: Fri, 15 Mar 2019 16:46:33 -0300 Subject: [PATCH 31/45] Add is-empty-card component and message when no chat is selected --- webchat/home/home.html | 1 + webchat/images/desenho-cis.png | Bin 0 -> 63421 bytes webchat/index.html | 6 +++- .../is-empty-card/is-empty-card.component.js | 24 ++++++++++++++++ webchat/utils/is-empty-card/is-empty-card.css | 26 ++++++++++++++++++ .../utils/is-empty-card/is-empty-card.html | 4 +++ 6 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 webchat/images/desenho-cis.png create mode 100644 webchat/utils/is-empty-card/is-empty-card.component.js create mode 100644 webchat/utils/is-empty-card/is-empty-card.css create mode 100644 webchat/utils/is-empty-card/is-empty-card.html diff --git a/webchat/home/home.html b/webchat/home/home.html index 8a81faeb5..3e4a418f3 100644 --- a/webchat/home/home.html +++ b/webchat/home/home.html @@ -10,4 +10,5 @@ selfie-stream="homeCtrl.currentChat.selfieStream" remote-stream="homeCtrl.currentChat.remoteStream"> + diff --git a/webchat/images/desenho-cis.png b/webchat/images/desenho-cis.png new file mode 100644 index 0000000000000000000000000000000000000000..958e63f9af8086e54aa65a10f12bd9f7096ec219 GIT binary patch literal 63421 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ðH}L1VzUQy;6)k^Pk0&egSdQi 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

$BSsDqaeXu_!bA-xbqX*)o4` z2eGFeAY~rGd!7fO6z0?g?X~!r4_F+k=9C2BI@-EISI&Z^_Jy>AMctXz8$z>ZVY!+h z^uvhsSp@&&F%AIz+zO8vpqLIThN{zMgRz@|P@SOoMD0mt5RG8H5NIvE@~$Nvz`31@ zfL~CM3*3Oz;U`or6Fx)W2yZ!;b)wel2>p2oiziBv znr{Jg1XI<5!hXV1*P~zO3sQw^!G;!)azddKR!R?w$^S1XlG~tG6zhiFk@AZ3zTrn# zXamosAzq=nD;FBqtPJG0z5`}E^1>P82cdaipHDB*9u+ICvEL^>4WQ5U+?dIG1{nWs zU!w;kGC;#|{=As$aV>CM&FpV;G`yeWdlJN0Sp6*kY3zXu88s)9W7{yAqHmc?`G)_Z zJMA>xDIn#zZ~BCeo>V0vNhkB*n)XmAo_30|VEc0b&dd1~ckH|GBM3;$(l_&d)LUp( zRB_O=)qqa}K`QOk)A1eydrA~{oN$FJI4=n#$Ya~c`b5-5uQDCrjJ$v*1!1enJmAX$ z>s#*tP6#ad6)otqoG|0_SS{%PD-w_*7v9P)l{&+MPu8G+hFre`9C0q-MV&M-#mQ(% zNZOxiP?#JAOI=*JG>{yAl`a$KF%=+_^h@!#rfU1iGWAMK^pEGDXVs`4Vd9WpCM5%- zzGNvGGRR@69Y}|^Fd`mWJd4?Qb{eSXc7V&d-+?nJE=AYUWeC>=!z4tyvEiYtxbELt zX9q}b*Q*qvkq}quUj?T}R2utKdX4-!d*;kdu-`C|M20jkumsA`hwlOvGbC%ucn9RF zB;-{@JtxVR|A!XST_B~cFrst&X-1Xt*qzgv^L~JpFK-~pb3kior3t;i{O;q+C;A6^ zBrfbGg@~4ly92&Vc@UEhI3UeRRz?JV+0U%@t@ACGL6c_*)^K|2sdP`SGh`mk! z^A33Yg4*RSXfqM@r(vAoUHOaQxOMgnDsR#mLX`wsQB z#?ph^c<#|kN`yqi?&e4bIKbgi#MM^g&$e&M!ihWP^tp)A=aO>y>yi{D4iH~o^_jq- zroqq{(D+YwsIB$+f$?m%)&H_YAUwL$ByzT@FXVki4r#KvTt8D-!|M?QmJE3fqAkfb zb&?yw9j1kmZcj{5w0CqlJcJCh1T=e-z+zv(4jEi{`IqMqi>(mcOvG?;!UGuWUoW|cesM=c4J0%jYbq)mDKp#j&A#xUXvvv6O zF+vCKnkCt-(2YX>Z9~Z15D%5w8x$^=l(*N+Z>IA??<}K#?p&wsP2dKg>0yWuXMaDX za6}U53HbAIDBvJ)mJ(aSb`LgRgZRn&_(9dge1-fOmZDJLjvS-`WF(#gqx*cSRg@s? t)))SIo;hDTs2_a$|L=b-+xv%3!-RRiA6DpRKM9TXrsiFZ5;dEb{{v - + + + @@ -150,6 +152,8 @@ + + diff --git a/webchat/utils/is-empty-card/is-empty-card.component.js b/webchat/utils/is-empty-card/is-empty-card.component.js new file mode 100644 index 000000000..2bbed5714 --- /dev/null +++ b/webchat/utils/is-empty-card/is-empty-card.component.js @@ -0,0 +1,24 @@ +(function () { + 'use strict'; + + /** + * Component that is shown when something is empty and in the error page. + * It's shows a default image and a text that it receives as a binding. + * @class isEmptyCard + * @example + * + */ + angular.module("webchat").component("isEmptyCard", { + templateUrl: "app/utils/is-empty-card/is-empty-card.html", + controller: isEmptyCardController, + controllerAs: "isEmptyCardCtrl", + bindings: { + text: "@" + }, + }); + + function isEmptyCardController() { + + } + +})(); \ No newline at end of file diff --git a/webchat/utils/is-empty-card/is-empty-card.css b/webchat/utils/is-empty-card/is-empty-card.css new file mode 100644 index 000000000..e211f97f9 --- /dev/null +++ b/webchat/utils/is-empty-card/is-empty-card.css @@ -0,0 +1,26 @@ +.is-empty-card__container { + display: grid; + grid-template-rows: auto auto; + grid-template-areas: + 'image' + 'text'; + width: 100%; + height: 100%; +} + +.is-empty-card__image { + grid-area: image; + width: 100%; +} + +.is-empty-card__text { + grid-area: text; + display: flex; + font-size: 1.5em; + margin: 1em 0; + padding: 0 0.5em; + font-weight: bold; + color: #009688; + text-align: center; + justify-content: center; +} \ No newline at end of file diff --git a/webchat/utils/is-empty-card/is-empty-card.html b/webchat/utils/is-empty-card/is-empty-card.html new file mode 100644 index 000000000..65a66249b --- /dev/null +++ b/webchat/utils/is-empty-card/is-empty-card.html @@ -0,0 +1,4 @@ +

+ + {{ isEmptyCardCtrl.text }} +
\ No newline at end of file From 8bbbbadd5a74763dbd029b31722090eee98e9abe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Esp=C3=ADndula?= Date: Fri, 15 Mar 2019 17:07:05 -0300 Subject: [PATCH 32/45] Add secure chat info message --- webchat/components/chat/body/chat-body.css | 9 +++++++++ webchat/components/chat/body/chat-body.html | 1 + 2 files changed, 10 insertions(+) diff --git a/webchat/components/chat/body/chat-body.css b/webchat/components/chat/body/chat-body.css index 98db2bf5b..f7b5c4ca9 100644 --- a/webchat/components/chat/body/chat-body.css +++ b/webchat/components/chat/body/chat-body.css @@ -20,6 +20,15 @@ width: 100%; } +.chat-body__info-message { + background-color: lightgoldenrodyellow; + width: fit-content; + padding: 10px 20px; + border-radius: 10px; + align-self: center; + margin: 10px 0 auto 0; +} + #video-remote { height: 100%; width: 100%; diff --git a/webchat/components/chat/body/chat-body.html b/webchat/components/chat/body/chat-body.html index b1a63ce13..1a0d50a47 100644 --- a/webchat/components/chat/body/chat-body.html +++ b/webchat/components/chat/body/chat-body.html @@ -1,4 +1,5 @@
+ Esta é uma conversa segura e não esta sendo gravada Date: Fri, 15 Mar 2019 17:18:16 -0300 Subject: [PATCH 33/45] Fix is-empty-card component size --- webchat/utils/is-empty-card/is-empty-card.css | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/webchat/utils/is-empty-card/is-empty-card.css b/webchat/utils/is-empty-card/is-empty-card.css index e211f97f9..194314844 100644 --- a/webchat/utils/is-empty-card/is-empty-card.css +++ b/webchat/utils/is-empty-card/is-empty-card.css @@ -1,16 +1,19 @@ .is-empty-card__container { display: grid; - grid-template-rows: auto auto; + grid-template-rows: 1fr 1fr; grid-template-areas: 'image' 'text'; width: 100%; height: 100%; + justify-content: center; } .is-empty-card__image { grid-area: image; - width: 100%; + max-height: 100%; + max-width: 100%; + align-self: flex-end; } .is-empty-card__text { From bf5ba8e35b393b4dddf2b4156a6a421ce1be2f9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Esp=C3=ADndula?= Date: Fri, 15 Mar 2019 17:20:48 -0300 Subject: [PATCH 34/45] Fix info message size --- webchat/components/chat/body/chat-body.css | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/webchat/components/chat/body/chat-body.css b/webchat/components/chat/body/chat-body.css index f7b5c4ca9..7528d3c00 100644 --- a/webchat/components/chat/body/chat-body.css +++ b/webchat/components/chat/body/chat-body.css @@ -21,12 +21,13 @@ } .chat-body__info-message { + text-align: center; background-color: lightgoldenrodyellow; width: fit-content; padding: 10px 20px; border-radius: 10px; align-self: center; - margin: 10px 0 auto 0; + margin: 10px 10px auto; } #video-remote { From 85227823d395ac21745dce6ca3a5e1ed9d993bee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Esp=C3=ADndula?= Date: Fri, 15 Mar 2019 17:32:03 -0300 Subject: [PATCH 35/45] Fix video responsive size --- webchat/components/chat/ecis-chat.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webchat/components/chat/ecis-chat.css b/webchat/components/chat/ecis-chat.css index 0cefd60d2..05d185115 100644 --- a/webchat/components/chat/ecis-chat.css +++ b/webchat/components/chat/ecis-chat.css @@ -2,7 +2,7 @@ height: 100%; background: #EEEEEE; display: grid; - grid-template-rows: max-content 1fr max-content; + grid-template-rows: 64px calc(100% - 64px - 50px) 50px; grid-template-areas: 'header' 'body' From 98925143440b8a13889b129155cb8767a25e2232 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Esp=C3=ADndula?= Date: Fri, 15 Mar 2019 17:33:08 -0300 Subject: [PATCH 36/45] Fix is-empty-card allign --- webchat/utils/is-empty-card/is-empty-card.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webchat/utils/is-empty-card/is-empty-card.css b/webchat/utils/is-empty-card/is-empty-card.css index 194314844..d7371b428 100644 --- a/webchat/utils/is-empty-card/is-empty-card.css +++ b/webchat/utils/is-empty-card/is-empty-card.css @@ -6,7 +6,7 @@ 'text'; width: 100%; height: 100%; - justify-content: center; + justify-items: center; } .is-empty-card__image { From aa9049fdc4b788f295b2e12ae99f6c5d258c3b16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Esp=C3=ADndula?= Date: Fri, 15 Mar 2019 17:53:03 -0300 Subject: [PATCH 37/45] Add audio management functions --- webchat/components/chat/body/chat-body.component.js | 11 +++++++++++ webchat/components/chat/body/chat-body.html | 2 +- .../components/chat/buttons/chat-buttons.component.js | 2 ++ webchat/components/chat/buttons/chat-buttons.html | 4 ++-- webchat/components/chat/ecis-chat.component.js | 8 ++++++++ webchat/components/chat/ecis-chat.html | 5 ++++- .../components/chat/header/chat-header.component.js | 2 ++ webchat/components/chat/header/chat-header.html | 4 +++- 8 files changed, 33 insertions(+), 5 deletions(-) diff --git a/webchat/components/chat/body/chat-body.component.js b/webchat/components/chat/body/chat-body.component.js index fda116370..f80dd9dee 100644 --- a/webchat/components/chat/body/chat-body.component.js +++ b/webchat/components/chat/body/chat-body.component.js @@ -8,6 +8,7 @@ bindings: { messages: '<', videoActive: '<', + audioActive: '<', selfieStream: '<', remoteStream: '<', }, @@ -19,6 +20,7 @@ chatBodyCtrl.$onChanges = (changesObj) => { updateSelfieVideo(changesObj); updateRemoteVideo(changesObj); + updateRemoteAudio(changesObj); }; const updateSelfieVideo = (changesObj) => { @@ -40,6 +42,15 @@ remoteVideo.play(); } }; + + const updateRemoteAudio = (changesObj) => { + const canUpdate = _.has(changesObj, 'audioActive'); + + if (canUpdate) { + const remoteVideo = document.getElementById('video-remote'); + remoteVideo.muted = changesObj.audioActive.currentValue; + } + }; } })(); \ No newline at end of file diff --git a/webchat/components/chat/body/chat-body.html b/webchat/components/chat/body/chat-body.html index 1a0d50a47..8c61a3da1 100644 --- a/webchat/components/chat/body/chat-body.html +++ b/webchat/components/chat/body/chat-body.html @@ -8,5 +8,5 @@
- +
diff --git a/webchat/components/chat/buttons/chat-buttons.component.js b/webchat/components/chat/buttons/chat-buttons.component.js index fa9bd908b..2c5f0a4f9 100644 --- a/webchat/components/chat/buttons/chat-buttons.component.js +++ b/webchat/components/chat/buttons/chat-buttons.component.js @@ -9,6 +9,8 @@ callFunc: "<", enableVideoFunc: "<", disableVideoFunc: "<", + enableAudioFunc: "<", + disableAudioFunc: "<", }, }); diff --git a/webchat/components/chat/buttons/chat-buttons.html b/webchat/components/chat/buttons/chat-buttons.html index 4aef7cd55..181219584 100644 --- a/webchat/components/chat/buttons/chat-buttons.html +++ b/webchat/components/chat/buttons/chat-buttons.html @@ -6,6 +6,6 @@ + action-on="chatButtonsCtrl.enableAudioFunc" + action-off="chatButtonsCtrl.disableAudioFunc"> diff --git a/webchat/components/chat/ecis-chat.component.js b/webchat/components/chat/ecis-chat.component.js index 19c137352..243ed7707 100644 --- a/webchat/components/chat/ecis-chat.component.js +++ b/webchat/components/chat/ecis-chat.component.js @@ -34,6 +34,14 @@ ecisChatCtrl.videoActive = true; }; + ecisChatCtrl.disableAudio = () => { + ecisChatCtrl.audioActive = false; + }; + + ecisChatCtrl.enableAudio = () => { + ecisChatCtrl.audioActive = true; + }; + } })(); \ No newline at end of file diff --git a/webchat/components/chat/ecis-chat.html b/webchat/components/chat/ecis-chat.html index 153b534ba..94080c253 100644 --- a/webchat/components/chat/ecis-chat.html +++ b/webchat/components/chat/ecis-chat.html @@ -4,12 +4,15 @@ user="ecisChatCtrl.user" call-func="ecisChatCtrl.call" enable-video-func="ecisChatCtrl.enableVideo" - disable-video-func="ecisChatCtrl.disableVideo"> + disable-video-func="ecisChatCtrl.disableVideo" + enable-audio-func="ecisChatCtrl.enableAudio" + disable-audio-func="ecisChatCtrl.disableAudio"> diff --git a/webchat/components/chat/header/chat-header.component.js b/webchat/components/chat/header/chat-header.component.js index 32fe45182..56c68d904 100644 --- a/webchat/components/chat/header/chat-header.component.js +++ b/webchat/components/chat/header/chat-header.component.js @@ -11,6 +11,8 @@ callFunc: "<", enableVideoFunc: '<', disableVideoFunc: '<', + enableAudioFunc: '<', + disableAudioFunc: '<', }, }); diff --git a/webchat/components/chat/header/chat-header.html b/webchat/components/chat/header/chat-header.html index 1e11de890..af0c36bb6 100644 --- a/webchat/components/chat/header/chat-header.html +++ b/webchat/components/chat/header/chat-header.html @@ -7,5 +7,7 @@ + disable-video-func="chatHeaderCtrl.disableVideoFunc" + enable-audio-func="chatHeaderCtrl.enableAudioFunc" + disable-audio-func="chatHeaderCtrl.disableAudioFunc"> From 270999d5acef6dc7fe779e41cc55223732903fdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Esp=C3=ADndula?= Date: Sun, 17 Mar 2019 14:50:44 -0300 Subject: [PATCH 38/45] Change info message color to match cis palette --- webchat/components/chat/body/chat-body.css | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/webchat/components/chat/body/chat-body.css b/webchat/components/chat/body/chat-body.css index 7528d3c00..bbc6aa3ab 100644 --- a/webchat/components/chat/body/chat-body.css +++ b/webchat/components/chat/body/chat-body.css @@ -22,7 +22,8 @@ .chat-body__info-message { text-align: center; - background-color: lightgoldenrodyellow; + background-color: #009688; + color: white; width: fit-content; padding: 10px 20px; border-radius: 10px; From 5ab56a4c9d7b535c1fb8f6ee43462739179cc9a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Esp=C3=ADndula?= Date: Sun, 17 Mar 2019 15:44:24 -0300 Subject: [PATCH 39/45] Move state responsability to chat object --- webchat/home/home.html | 2 +- webchat/home/homeController.js | 1 - webchat/utils/chat.js | 8 +++++++- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/webchat/home/home.html b/webchat/home/home.html index 3e4a418f3..f66c74149 100644 --- a/webchat/home/home.html +++ b/webchat/home/home.html @@ -6,7 +6,7 @@ chat="homeCtrl.currentChat" user="homeCtrl.currentUser" call-func="homeCtrl.call" - state="homeCtrl.state" + state="homeCtrl.currentChat.connectionState" selfie-stream="homeCtrl.currentChat.selfieStream" remote-stream="homeCtrl.currentChat.remoteStream"> diff --git a/webchat/home/homeController.js b/webchat/home/homeController.js index 7bcff2c8c..cef4fd162 100644 --- a/webchat/home/homeController.js +++ b/webchat/home/homeController.js @@ -52,7 +52,6 @@ }; homeCtrl.stateChange = (state) => { - homeCtrl.state = state; $scope.$apply(); }; diff --git a/webchat/utils/chat.js b/webchat/utils/chat.js index cc210f8d0..fcb00ea57 100644 --- a/webchat/utils/chat.js +++ b/webchat/utils/chat.js @@ -30,6 +30,7 @@ this.rpc.onsignalingstatechange = this.handleState.bind(this); this._currentMessages = []; this._remoteStream = {}; + this._connectionState = ""; } get remoteStream() { @@ -40,6 +41,10 @@ return this._selfStream; } + get connectionState() { + return this._connectionState; + } + /** * Mimics node's on/emit event handlers. * Raises an event. @@ -119,7 +124,8 @@ * @fires Chat#ice-connection-changed */ handleIceConnectionState(ev) { - this.emit('ice-connection-changed', ev.target.iceConnectionState) + this._connectionState = ev.target.iceConnectionState; + this.emit('ice-connection-changed', ev.target.iceConnectionState); } /** From ab2cf962a5bf92ddd75222b4b9166a9b6947a18e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Esp=C3=ADndula?= Date: Sun, 17 Mar 2019 16:17:04 -0300 Subject: [PATCH 40/45] Add no-video-message --- webchat/components/chat/body/chat-body.css | 14 ++++++++++++++ webchat/components/chat/body/chat-body.html | 7 +++++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/webchat/components/chat/body/chat-body.css b/webchat/components/chat/body/chat-body.css index bbc6aa3ab..982f2197d 100644 --- a/webchat/components/chat/body/chat-body.css +++ b/webchat/components/chat/body/chat-body.css @@ -13,6 +13,7 @@ } .chat-body__video-container { + display: grid; background-color: darkgray; max-width: 100%; max-height: 100%; @@ -31,6 +32,19 @@ margin: 10px 10px auto; } +.chat-body__no-video-message { + background-color: #009688; + color: white; + border-radius: 10px; + padding: 10px 20px; + height: fit-content; + width: fit-content; + margin: 10px; + text-align: center; + align-self: center; + justify-self: center; +} + #video-remote { height: 100%; width: 100%; diff --git a/webchat/components/chat/body/chat-body.html b/webchat/components/chat/body/chat-body.html index 8c61a3da1..f9c0eac60 100644 --- a/webchat/components/chat/body/chat-body.html +++ b/webchat/components/chat/body/chat-body.html @@ -7,6 +7,9 @@
- - +
+ + +
+ Nenhum vídeo está sendo recebido
From 679211012b2c9bc90d06456a60146f3c134d2ca6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Esp=C3=ADndula?= Date: Sun, 17 Mar 2019 16:30:48 -0300 Subject: [PATCH 41/45] Fix chat body scroll --- webchat/components/chat/body/chat-body.css | 7 ++++++- webchat/components/chat/body/chat-body.html | 18 ++++++++++-------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/webchat/components/chat/body/chat-body.css b/webchat/components/chat/body/chat-body.css index 982f2197d..fbde78d32 100644 --- a/webchat/components/chat/body/chat-body.css +++ b/webchat/components/chat/body/chat-body.css @@ -3,12 +3,17 @@ width: 100%; } +.chat-body__messages-wrapper { + overflow-y: auto; + max-height: 100%; + max-width: 100%; +} + .chat-body__messages-container { height: 100%; max-height: 100%; display: flex; flex-direction: column; - overflow-y: scroll; justify-content: flex-end; } diff --git a/webchat/components/chat/body/chat-body.html b/webchat/components/chat/body/chat-body.html index f9c0eac60..f864d89a5 100644 --- a/webchat/components/chat/body/chat-body.html +++ b/webchat/components/chat/body/chat-body.html @@ -1,11 +1,13 @@ -
- Esta é uma conversa segura e não esta sendo gravada - - -
+
+
+ Esta é uma conversa segura e não esta sendo gravada + + +
+
From 47c9203268d94badc8200fd7641858cf0e54fd13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Esp=C3=ADndula?= Date: Sun, 17 Mar 2019 16:31:05 -0300 Subject: [PATCH 42/45] Fix layout break when word is too big --- webchat/components/chat/message/chat-message.css | 1 + 1 file changed, 1 insertion(+) diff --git a/webchat/components/chat/message/chat-message.css b/webchat/components/chat/message/chat-message.css index 0c244fb3e..498676fc7 100644 --- a/webchat/components/chat/message/chat-message.css +++ b/webchat/components/chat/message/chat-message.css @@ -14,6 +14,7 @@ .chat-message__text { grid-area: text; + word-break: break-word; } .chat-message__time { From 40fe2c5df3e9a25a16ca706baf6ca06e7299bf84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Esp=C3=ADndula?= Date: Sun, 17 Mar 2019 16:32:12 -0300 Subject: [PATCH 43/45] Fix info-message margin --- webchat/components/chat/body/chat-body.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webchat/components/chat/body/chat-body.css b/webchat/components/chat/body/chat-body.css index fbde78d32..7bfd72bec 100644 --- a/webchat/components/chat/body/chat-body.css +++ b/webchat/components/chat/body/chat-body.css @@ -34,7 +34,7 @@ padding: 10px 20px; border-radius: 10px; align-self: center; - margin: 10px 10px auto; + margin: 10px 10px; } .chat-body__no-video-message { From 5db1a6b66f1d2b1506e5798e8c7051214d856885 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Esp=C3=ADndula?= Date: Sun, 17 Mar 2019 16:35:19 -0300 Subject: [PATCH 44/45] Fix div definition on chat body --- webchat/components/chat/body/chat-body.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webchat/components/chat/body/chat-body.html b/webchat/components/chat/body/chat-body.html index f864d89a5..47831ae3b 100644 --- a/webchat/components/chat/body/chat-body.html +++ b/webchat/components/chat/body/chat-body.html @@ -7,7 +7,7 @@ message-obj="message">
- +
From 14597913c29c75e882de8542fdc78e74d694efe0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Esp=C3=ADndula?= Date: Mon, 18 Mar 2019 12:54:21 -0300 Subject: [PATCH 45/45] Change websocket url to production --- webchat/app.js | 3 +-- webchat/utils/chatClient.js | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/webchat/app.js b/webchat/app.js index 11431b295..1dcd7984f 100644 --- a/webchat/app.js +++ b/webchat/app.js @@ -17,8 +17,7 @@ }); app.constant('WEBSOCKET', { - hostname: window.location.hostname, - port: 8090, + url: "ws://webchat-server-dot-development-cis.appspot.com/", maxRetries: 5, }); diff --git a/webchat/utils/chatClient.js b/webchat/utils/chatClient.js index 1770a30f8..65fbc2051 100644 --- a/webchat/utils/chatClient.js +++ b/webchat/utils/chatClient.js @@ -57,7 +57,7 @@ * Starts a new connection with the websocket server. */ startWebsocket() { - const websocket = `ws://${WEBSOCKET.hostname}:${WEBSOCKET.port}`; + const websocket = WEBSOCKET.url; this.ws = new WebSocket(websocket); // Sends a signin message to the websocket as soon as the websocket connects