diff --git a/app/locales.js b/app/locales.js index bce2272b..7998bc24 100644 --- a/app/locales.js +++ b/app/locales.js @@ -192,7 +192,6 @@ Ext.define('Ngcp.csc.locales', { sp: 'all calls' } }, - filters: { search: { en: 'SEARCH', @@ -237,7 +236,6 @@ Ext.define('Ngcp.csc.locales', { sp: 'reset filter' } }, - voicemails: { title: { en: 'Your voicemail recordings.', @@ -305,7 +303,7 @@ Ext.define('Ngcp.csc.locales', { fr: 'Send notification of new messages to the following e-mail adddress', sp: 'Send notification of new messages to the following e-mail adddress' }, - attach_recording:{ + attach_recording: { en: 'attach recording', it: 'attach recording', de: 'attach recording', @@ -336,6 +334,31 @@ Ext.define('Ngcp.csc.locales', { } }, + chat: { + title: { + en: 'Team', + it: 'Team', + de: 'Team', + fr: 'Team', + sp: 'Team' + }, + msg_box:{ + empty_text:{ + en: 'Type a message', + it: 'Type a message', + de: 'Type a message', + fr: 'Type a message', + sp: 'Type a message' + } + }, + start_conversation: { + en: 'You can start a private conversation with {0} here.', + it: 'You can start a private conversation with {0} here.', + de: 'You can start a private conversation with {0} here.', + fr: 'You can start a private conversation with {0} here.', + sp: 'You can start a private conversation with {0} here.' + } + }, common: { today: { en: 'Today', @@ -372,14 +395,14 @@ Ext.define('Ngcp.csc.locales', { fr: 'no', sp: 'no' }, - delete:{ + delete: { en: 'Delete', it: 'Delete', de: 'Delete', fr: 'Delete', sp: 'Delete' }, - listen:{ + listen: { en: 'Listen', it: 'Listen', de: 'Listen', @@ -413,7 +436,15 @@ Ext.define('Ngcp.csc.locales', { de: 'Successfully removed.', fr: 'Successfully removed.', sp: 'Successfully removed.' + }, + submit: { + en: 'submit', + it: 'submit', + de: 'submit', + fr: 'submit', + sp: 'submit' } + } } }) diff --git a/app/model/ChatList.js b/app/model/ChatList.js new file mode 100644 index 00000000..e3db42cf --- /dev/null +++ b/app/model/ChatList.js @@ -0,0 +1,16 @@ +Ext.define('NgcpCsc.model.ChatList', { + extend: 'Ext.data.Model', + fields: [{ + type: 'int', + name: 'id' + }, { + type: 'string', + name: 'name' + }, { + type: 'string', + name: 'thumbnail' + }, { + type: 'boolean', + name: 'online' + }] +}); diff --git a/app/model/ChatNotification.js b/app/model/ChatNotification.js new file mode 100644 index 00000000..d763e25c --- /dev/null +++ b/app/model/ChatNotification.js @@ -0,0 +1,20 @@ +Ext.define('NgcpCsc.model.ChatNotification', { + extend: 'Ext.data.Model', + fields: [{ + name: '_id' + }, { + name: 'parent_id' + }, { + name: 'name' + }, { + name: 'source' + }, { + name: 'date' + }, { + name: 'isActive' + }, { + name: 'time' + }, { + name: 'content' + }] +}); diff --git a/app/store/Chat.js b/app/store/Chat.js new file mode 100644 index 00000000..52bcfef6 --- /dev/null +++ b/app/store/Chat.js @@ -0,0 +1,18 @@ +Ext.define('NgcpCsc.store.Chat', { + extend: 'Ext.data.Store', + + storeId: 'Chat', + + model: 'NgcpCsc.model.ChatNotification', + + autoLoad: true, + + proxy: { + type: 'ajax', + url: '/resources/data/chat.json', + reader: { + type: 'json', + rootProperty: 'data' + } + } +}); diff --git a/app/store/ChatList.js b/app/store/ChatList.js new file mode 100644 index 00000000..d76234b9 --- /dev/null +++ b/app/store/ChatList.js @@ -0,0 +1,25 @@ +Ext.define('NgcpCsc.store.ChatList', { + extend: 'Ext.data.Store', + + alias: 'store.chatlist', + + storeId: 'ChatList', + + model: 'NgcpCsc.model.ChatList', + + autoLoad: true, + + proxy: { + type: 'ajax', + url: '/resources/data/chatlist.json', + reader: { + type: 'json', + rootProperty: 'data' + } + }, + + sorters: { + direction: 'DESC', + property: 'online' + } +}); diff --git a/app/store/NavigationTree.js b/app/store/NavigationTree.js index ce15b9d6..79051d8a 100644 --- a/app/store/NavigationTree.js +++ b/app/store/NavigationTree.js @@ -27,6 +27,12 @@ Ext.define('NgcpCsc.store.NavigationTree', { viewType: 'voicemails', routeId: 'voicebox', leaf: true + }, { + text: 'Chat', + iconCls: 'x-fa fa-wechat', + viewType: 'chat', + routeId: 'chat', + leaf: true }, { text: 'Address book', iconCls: 'x-fa fa-book', diff --git a/classic/sass/src/view/main/Main.scss b/classic/sass/src/view/main/Main.scss index 603b10c9..c8a903c3 100644 --- a/classic/sass/src/view/main/Main.scss +++ b/classic/sass/src/view/main/Main.scss @@ -334,3 +334,6 @@ .red-txt { color: red; } +.x-panel-bodyWrap { + background-color: white; +} diff --git a/classic/sass/src/view/pages/chat/Chat.scss b/classic/sass/src/view/pages/chat/Chat.scss new file mode 100644 index 00000000..65b31f02 --- /dev/null +++ b/classic/sass/src/view/pages/chat/Chat.scss @@ -0,0 +1,295 @@ +.user-notifications { + padding: 15px; + background: #fff; + + .x-view-item-focused { + outline: 0 !important; + } +} + +.timeline-item { + .line-wrap { + position: relative; + + &:before { + content: ""; + position: absolute; + width: 50px; + height: 100%; + border-right: solid 2px $base-color; + left: 0; + top: 70px; + } + } + + .profile-pic-wrap { + width: 100px; + float: left; + font-size: 10px; + text-align: center; + position: relative; + background: $lightest-color; + padding: 5px 0; + + img { + height: 46px; + width: 46px; + @include border-radius($circle-border-radius); + } + } + + .contents-wrap { + margin: 0 0 15px 110px; + border: 1px solid $base-border-color; + position: relative; + padding: 15px; + white-space: normal; + + &:after, + &:before { + content: ""; + display: block; + position: absolute; + width: 0; + height: 0; + left: 0; + top: 0; + } + + &:before { + border-top: 9px solid transparent; + border-bottom: 9px solid transparent; + border-right: 9px solid $base-border-color; + margin: 15px 0 0 -9px; + } + + &:after { + border-top: 9px solid transparent; + border-bottom: 9px solid transparent; + border-right: 9px solid $lightest-color; + margin: 15px 0 0 -8px; + } + + .followed-by, + .shared-by { + font-size: 12px; + line-height: 18px; + margin-bottom: 10px; + + a { + color: $base-color; + text-decoration: none; + font-weight: bold; + } + } + + .shared-img { + width: 100%; + display: block; + } + + .job-meeting a { + color: $base-color; + text-decoration: none; + font-weight: bold; + margin-bottom: 10px; + display: block; + } + } + + .article-comment { + border-left: 5px solid $base-border-color; + padding: 10px 20px; + + span { + margin-right: 10px; + color: $article-comment-color; + } + } + + .followed-by { + img { + height: 32px; + width: 32px; + @include border-radius($circle-border-radius); + margin-right: 5px; + float: left; + } + + .followed-by-inner { + margin-left: 40px; + padding: 5px 0; + } + } +} + +.comments { + margin-bottom: 10px; + cursor: pointer; + + img.profile-icon { + height: 50px; + width: 50px; + border: 2px solid $base-border-color; + @include border-radius($circle-border-radius); + float: left; + } + + h4 { + font-size: 14px; + float: left; + + span { + margin-left: 8px; + font-size: 18px; + position: relative; + top: 2px; + } + } + + .from-now { + float: right; + + span { + margin-right: 5px; + font-size: 16px; + position: relative; + top: 1px; + } + } + + .content-wrap { + margin-left: 65px; + + .content { + margin-bottom: 15px; + white-space: normal; + clear: both; + } + } + + &.sub-comments { + margin: 0 0 10px 60px; + + .like-comment-btn-wrap { + border-bottom: none; + } + } +} + +.like-comment-btn-wrap { + text-align: right; + padding-bottom: 15px; + + button { + margin-left: 15px; + height: 30px; + width: 30px; + background-color: $like-comment-btn-background-color; + font-size: 14px; + color: $color; + border: none; + cursor: pointer; + } +} + +.menu-item-common { + padding-right: 20px; + position: absolute; + font-family: FontAwesome; + right: 0; +} + +.navigation-email { + // @include box-shadow(0px,2px,8px,0px,rgba(0,0,0,.15)); + @include box-shadow(0, 1px, 2px, 0, rgba(0,0,0,0.2)); + + &.x-menu-default { + border-width: 0; + } + + .x-menu-header { + line-height: 20px; + background-color: $lightest-color; + padding: 22px 15px; + border-bottom: 1px solid #ccc !important; + + .x-title-icon-wrap { + width: 40px; + padding-right: 28px; + text-align: left; + } + + .x-title-text { + font-size: 16px; + } + } + + .x-menu-item-icon-default { + padding-top: 10px; + padding-left: 12px; + font-size: 16px; + line-height: 24px; + } + + .x-menu-item { + line-height: 50px; + + .x-menu-item-text-default.x-menu-item-indent-no-separator { + margin-left: 56px; + } + + &.online-user { + .x-menu-item-text-default.x-menu-item-indent-no-separator { + margin-left: 18px; + } + + .x-menu-item-link:after { + color: $online-menu-item-color; + content: "\f111"; + @extend .menu-item-common; + } + + &.x-menu-item-active { + .x-menu-item-link:after { + color: white; + } + } + } + + &.offline-user { + .x-menu-item-link:after { + color: $offline-menu-item-color; + content: "\f111"; + @extend .menu-item-common; + } + + .x-menu-item-text-default.x-menu-item-indent-no-separator { + margin-left: 20px; + } + } + + .x-menu-item-link:after { + @extend .menu-item-common; + color: $default-menu-item-color; + content: "\f105"; + } + } +} + +.new-message-cont { + margin-left: 203px; +} + +.submit-new-message { + float: right; + top: 0 !important; +} + +.hide-pm { + display: none; +} + +.private-conversation-text { + padding: 30px; +} diff --git a/classic/sass/var/login/Login.scss b/classic/sass/var/login/Login.scss deleted file mode 100644 index 07ad1c2a..00000000 --- a/classic/sass/var/login/Login.scss +++ /dev/null @@ -1 +0,0 @@ -$dialog-trigger-color : #e5e5e5; diff --git a/classic/sass/var/view/main/Main.scss b/classic/sass/var/view/main/Main.scss index c97c1c65..25f0b9ea 100644 --- a/classic/sass/var/view/main/Main.scss +++ b/classic/sass/var/view/main/Main.scss @@ -14,3 +14,8 @@ $social-envelope-btn-background: #7754aa; // custom vars $console-msg-font-size: 16px; + +$default-menu-item-color: #858585; +$online-menu-item-color: #86c747; +$offline-menu-item-color: #9e9f9f; +$menu-item-focus-color: #88bf4c; diff --git a/classic/sass/var/pages/BlankPage.scss b/classic/sass/var/view/pages/BlankPage.scss similarity index 100% rename from classic/sass/var/pages/BlankPage.scss rename to classic/sass/var/view/pages/BlankPage.scss diff --git a/classic/sass/var/view/pages/chat/Chat.scss b/classic/sass/var/view/pages/chat/Chat.scss new file mode 100644 index 00000000..3e496546 --- /dev/null +++ b/classic/sass/var/view/pages/chat/Chat.scss @@ -0,0 +1,3 @@ +$like-comment-btn-background-color: #f4f4f4; +$like-comment-btn-color: #979797; +$article-comment-color: #eee; diff --git a/classic/src/Application.js b/classic/src/Application.js index 2c74edc9..4dc62857 100644 --- a/classic/src/Application.js +++ b/classic/src/Application.js @@ -19,6 +19,8 @@ Ext.define('NgcpCsc.Application', { 'NavigationTree', 'Calls', 'VoiceMails', + 'Chat', + 'ChatList', 'Languages' ], diff --git a/classic/src/view/main/Main.js b/classic/src/view/main/Main.js index 38577f3c..35c87981 100644 --- a/classic/src/view/main/Main.js +++ b/classic/src/view/main/Main.js @@ -78,6 +78,7 @@ Ext.define('NgcpCsc.view.main.Main', { id: 'main-view-detail-wrap', reference: 'mainContainerWrap', flex: 1, + scrollable: false, items: [{ xtype: 'treelist', reference: 'navigationTreeList', @@ -96,9 +97,9 @@ Ext.define('NgcpCsc.view.main.Main', { reference: 'mainCardPanel', cls: 'sencha-dash-right-main-container', itemId: 'contentPanel', + height: 500, layout: { - type: 'card', - anchor: '100%' + type: 'card' } }] }] diff --git a/classic/src/view/pages/chat/Chat.js b/classic/src/view/pages/chat/Chat.js new file mode 100644 index 00000000..cbbd5f55 --- /dev/null +++ b/classic/src/view/pages/chat/Chat.js @@ -0,0 +1,60 @@ +Ext.define('NgcpCsc.view.pages.chat.Chat', { + extend: 'Ext.panel.Panel', + + xtype: 'chat', + + viewModel: 'chat', + + controller: 'chat', + + layout: 'hbox', + + items: [{ + xtype: 'chatlist', + width: 200, + padding: '10 20 20', + height: '100%' + }, { + xtype: 'tabpanel', + width: '90%', + height: '100%', + items: [{ + title: Ngcp.csc.locales.chat.title[localStorage.getItem('languageSelected')], + xtype: 'chat-notifications', + id: 'chat-notifications', + scrollable: true, + bind: { + store: '{notifications}' + } + }] + }], + + dockedItems: [{ + xtype: 'toolbar', + cls: 'new-message-cont', + fixed: true, + padding: '0 0 10 0', + dock: 'bottom', + items: [{ + xtype: 'textarea', + bind: { + value: '{new_message}' + }, + cls: 'new-message', + name: 'new-message', + enableKeyEvents: true, + height: 100, + width: '95%', + listeners: { + keypress: 'onPressEnter' + }, + emptyText: Ngcp.csc.locales.chat.msg_box.empty_text[localStorage.getItem('languageSelected')] + }, { + xtype: 'button', + cls: 'submit-new-message', + text: Ngcp.csc.locales.common.submit[localStorage.getItem('languageSelected')], + handler: 'onPressSubmitBtn' + }] + }] + +}); diff --git a/classic/src/view/pages/chat/ChatController.js b/classic/src/view/pages/chat/ChatController.js new file mode 100644 index 00000000..cbf98ecb --- /dev/null +++ b/classic/src/view/pages/chat/ChatController.js @@ -0,0 +1,80 @@ +Ext.define('NgcpCsc.view.pages.chat.ChatController', { + extend: 'Ext.app.ViewController', + + alias: 'controller.chat', + + listen: { + controller: { + '#chatlist': { + openpmtab: 'openPM' + } + } + }, + + onPressEnter: function(field, e) { + if (e.getKey() == e.ENTER) { + e.preventDefault(); + this.submitMessage(); + } + }, + + onPressSubmitBtn: function(field, e) { + this.submitMessage(); + }, + + submitMessage: function(msg, user) { + var message = msg || this.getViewModel().get('new_message'); + if (message.length < 1) + return; + var chatStore = this.getView().down('tabpanel').getActiveTab().getStore('notifications'); + var lastMsg = chatStore.getAt(chatStore.getCount() - 1) || this.getViewModel().getStore('notifications').findRecord('id', this.getView().down('tabpanel').getActiveTab().name); + var date = new Date(); + var minutes = date.getMinutes(); + var hour = date.getHours(); + var day = date.getDate(); + var month = date.getMonth() + 1; + var messageModel = Ext.create('NgcpCsc.model.ChatNotification', { + /// "id": (user) ? user.get('id') : 0, + "name": (user) ? user.get('name') : localStorage.getItem('username'), + "date": Ext.String.format("{0}.{1}", day, month), + "isActive": true, + "time": Ext.String.format("{0}:{1}", hour, minutes), + "thumbnail": (user) ? user.get('thumbnail') : "resources/images/user-profile/2.png", + "content": message + }); + chatStore.add(messageModel); + this.clearMsg(); + this.focusLastMsg(); + }, + + clearMsg: function() { + this.getView().down('[name=new-message]').reset(); + }, + + focusLastMsg: function(rec) { + var chatCmp = this.getView().down('tabpanel').getActiveTab(); + chatCmp.scrollTo(0, chatCmp.getEl().dom.scrollHeight); + }, + + openPM: function(item, rec) { + var tab = this.getView().down('[name=' + rec.get('id') + ']'); + if (rec.get('name') == 'administrator') // hardcoded administrator + return; + if (!tab) { + tab = this.getView().down('tabpanel').add({ + xtype: 'chat-notifications', + title: rec.get('name'), + closable: true, + scrollable: true, + cls: 'private-conversation-text', + deferEmptyText: false, + emptyText: Ext.String.format(Ngcp.csc.locales.chat.start_conversation[localStorage.getItem('languageSelected')], rec.get('name')), + name: rec.get('id'), + store: Ext.create('Ext.data.Store', { + model: 'NgcpCsc.model.ChatNotification' + }) + }); + } + this.getView().down('tabpanel').setActiveTab(tab); + } +}); diff --git a/classic/src/view/pages/chat/ChatList.js b/classic/src/view/pages/chat/ChatList.js new file mode 100644 index 00000000..c1edfcd7 --- /dev/null +++ b/classic/src/view/pages/chat/ChatList.js @@ -0,0 +1,23 @@ +Ext.define('NgcpCsc.view.pages.chat.ChatList', { + extend: 'Ext.menu.Menu', + + alias: 'widget.chatlist', + + viewModel: { + type: 'chatlist' + }, + + controller: 'chatlist', + + title: Ngcp.csc.locales.chat.title[localStorage.getItem('languageSelected')], + + cls: 'navigation-email', + + iconCls: 'x-fa fa-group', + + floating: false, + + listeners: { + click: 'itemListClicked' + } +}); diff --git a/classic/src/view/pages/chat/ChatListController.js b/classic/src/view/pages/chat/ChatListController.js new file mode 100644 index 00000000..d7410d4e --- /dev/null +++ b/classic/src/view/pages/chat/ChatListController.js @@ -0,0 +1,51 @@ +Ext.define('NgcpCsc.view.pages.chat.ChatListController', { + extend: 'Ext.app.ViewController', + + alias: 'controller.chatlist', + + id: 'chatlist', // needed as reference in ChatController listeners + + init: function() { + var me = this, + friendsStore = me.getViewModel().getStore('friends'); + + //Trigger local sorting once new data is available + friendsStore.on('load', function(store) { + store.sort(); + }); + + //Sort locally and then update menu + friendsStore.on('sort', function(store) { + me.mutateData(store, store.getRange()); + }); + + me.callParent(arguments); + }, + + mutateData: function(store, records) { + var view = this.getView(), + arr = [], + len = records.length, + i; + + for (i = 0; i < len; i++) { + arr.push({ + xtype: 'menuitem', + uId: records[i].get('id'), + text: records[i].get('name'), + cls: 'font-icon ' + (records[i].get('online') ? 'online-user' : 'offline-user') + }); + } + + Ext.suspendLayouts(); + view.removeAll(true); + view.add(arr); + Ext.resumeLayouts(true); + }, + + itemListClicked: function(menu, item) { + var selectedUser = Ext.getStore('ChatList').findRecord('id', item.uId, 0, false, true, true); + if (selectedUser && selectedUser.get('online')) + this.fireEvent('openpmtab', null, selectedUser); + } +}); diff --git a/classic/src/view/pages/chat/ChatListModel.js b/classic/src/view/pages/chat/ChatListModel.js new file mode 100644 index 00000000..0c72e5f7 --- /dev/null +++ b/classic/src/view/pages/chat/ChatListModel.js @@ -0,0 +1,15 @@ +Ext.define('NgcpCsc.view.pages.chat.ChatListModel', { + extend: 'Ext.app.ViewModel', + + alias: 'viewmodel.chatlist', + + stores: { + friends: { + //Store reference + type: 'chatlist', + + //Auto load + autoLoad: true + } + } +}); diff --git a/classic/src/view/pages/chat/ChatModel.js b/classic/src/view/pages/chat/ChatModel.js new file mode 100644 index 00000000..932dee6f --- /dev/null +++ b/classic/src/view/pages/chat/ChatModel.js @@ -0,0 +1,11 @@ +Ext.define('NgcpCsc.view.pages.chat.ChatModel', { + extend: 'Ext.app.ViewModel', + alias: 'viewmodel.chat', + data: { + new_message:'' + }, + formulas: {}, + stores: { + notifications: 'Chat' + } +}); diff --git a/classic/src/view/pages/chat/ChatNotifications.js b/classic/src/view/pages/chat/ChatNotifications.js new file mode 100644 index 00000000..ade8434d --- /dev/null +++ b/classic/src/view/pages/chat/ChatNotifications.js @@ -0,0 +1,28 @@ +Ext.define('NgcpCsc.view.pages.chat.ChatNotifications', { + extend: 'Ext.DataView', + xtype: 'chat-notifications', + + cls: 'user-notifications', + + scrollable: false, + + listeners: { + itemclick: 'openPM' + }, + + itemTpl: [ + "
{name}
", + "{time} {date}", + "", // hide the private message button in case the message comes from the user (in this case _d = 0); + "", + "", + "