From 595b16275bd63f333b5eb3a68994022e839dde99 Mon Sep 17 00:00:00 2001 From: Sercan Yemen Date: Tue, 3 Jul 2018 18:23:51 +0300 Subject: [PATCH] (Chat Panel) Added a chat panel --- src/app/app.component.scss | 4 +- src/app/fake-db/chat-panel.ts | 323 ++++++++++++++++++ src/app/fake-db/fake-db.service.ts | 6 + .../chat-panel/chat-panel.component.html | 105 ++++++ .../chat-panel/chat-panel.component.scss | 218 ++++++++++++ .../chat-panel/chat-panel.component.ts | 217 ++++++++++++ .../chat-panel/chat-panel.module.ts | 33 ++ .../chat-panel/chat-panel.service.ts | 182 ++++++++++ .../components/toolbar/toolbar.component.html | 11 +- .../components/toolbar/toolbar.component.scss | 1 + .../components/toolbar/toolbar.component.ts | 23 ++ .../layout-1/layout-1.component.html | 8 + .../layout-1/layout-1.component.scss | 1 + .../horizontal/layout-1/layout-1.module.ts | 2 + .../vertical/layout-1/layout-1.component.html | 8 + .../vertical/layout-1/layout-1.component.scss | 1 + .../vertical/layout-1/layout-1.module.ts | 2 + .../vertical/layout-2/layout-2.component.html | 8 + .../vertical/layout-2/layout-2.component.scss | 1 + .../vertical/layout-2/layout-2.module.ts | 2 + .../vertical/layout-3/layout-3.component.html | 8 + .../vertical/layout-3/layout-3.component.scss | 1 + .../vertical/layout-3/layout-3.module.ts | 2 + 23 files changed, 1164 insertions(+), 3 deletions(-) create mode 100644 src/app/fake-db/chat-panel.ts create mode 100644 src/app/layout/components/chat-panel/chat-panel.component.html create mode 100644 src/app/layout/components/chat-panel/chat-panel.component.scss create mode 100644 src/app/layout/components/chat-panel/chat-panel.component.ts create mode 100644 src/app/layout/components/chat-panel/chat-panel.module.ts create mode 100644 src/app/layout/components/chat-panel/chat-panel.service.ts diff --git a/src/app/app.component.scss b/src/app/app.component.scss index 42a78d3d..6e7a0eff 100644 --- a/src/app/app.component.scss +++ b/src/app/app.component.scss @@ -9,7 +9,7 @@ .theme-options-button { position: absolute; top: 160px; - right: 0; + right: 70px; width: 48px; height: 48px; line-height: 48px; @@ -18,7 +18,7 @@ border-radius: 0; margin: 0; pointer-events: auto; - opacity: .75; + opacity: .90; z-index: 998; mat-icon { diff --git a/src/app/fake-db/chat-panel.ts b/src/app/fake-db/chat-panel.ts new file mode 100644 index 00000000..878c69a1 --- /dev/null +++ b/src/app/fake-db/chat-panel.ts @@ -0,0 +1,323 @@ +export class ChatPanelFakeDb +{ + public static contacts = [ + { + 'id' : '5725a680b3249760ea21de52', + 'name' : 'Alice Freeman', + 'avatar': 'assets/images/avatars/alice.jpg', + 'status': 'online', + 'mood' : 'Lorem ipsum dolor sit amet, consectetur adipiscing elit...', + 'unread': '2' + }, + { + 'id' : '5725a680606588342058356d', + 'name' : 'Arnold', + 'avatar': 'assets/images/avatars/Arnold.jpg', + 'status': 'do-not-disturb', + 'mood' : 'Lorem ipsum dolor sit amet, consectetur adipiscing elit...' + }, + { + 'id' : '5725a68009e20d0a9e9acf2a', + 'name' : 'Barrera', + 'avatar': 'assets/images/avatars/Barrera.jpg', + 'status': 'do-not-disturb', + 'mood' : 'Lorem ipsum dolor sit amet, consectetur adipiscing elit...' + }, + { + 'id' : '5725a6809fdd915739187ed5', + 'name' : 'Blair', + 'avatar': 'assets/images/avatars/Blair.jpg', + 'status': 'offline', + 'mood' : 'Lorem ipsum dolor sit amet, consectetur adipiscing elit...', + 'unread': '3' + }, + { + 'id' : '5725a68007920cf75051da64', + 'name' : 'Boyle', + 'avatar': 'assets/images/avatars/Boyle.jpg', + 'status': 'offline', + 'mood' : 'Lorem ipsum dolor sit amet, consectetur adipiscing elit...', + 'unread': '1' + }, + { + 'id' : '5725a68031fdbb1db2c1af47', + 'name' : 'Christy', + 'avatar': 'assets/images/avatars/Christy.jpg', + 'status': 'offline', + 'mood' : 'Lorem ipsum dolor sit amet, consectetur adipiscing elit...' + }, + { + 'id' : '5725a680bc670af746c435e2', + 'name' : 'Copeland', + 'avatar': 'assets/images/avatars/Copeland.jpg', + 'status': 'online', + 'mood' : 'Lorem ipsum dolor sit amet, consectetur adipiscing elit...' + }, + { + 'id' : '5725a680e7eb988a58ddf303', + 'name' : 'Estes', + 'avatar': 'assets/images/avatars/Estes.jpg', + 'status': 'away', + 'mood' : 'Lorem ipsum dolor sit amet, consectetur adipiscing elit...' + }, + { + 'id' : '5725a680dcb077889f758961', + 'name' : 'Harper', + 'avatar': 'assets/images/avatars/Harper.jpg', + 'status': 'offline', + 'mood' : 'Lorem ipsum dolor sit amet, consectetur adipiscing elit...' + }, + { + 'id' : '5725a6806acf030f9341e925', + 'name' : 'Helen', + 'avatar': 'assets/images/avatars/Helen.jpg', + 'status': 'away', + 'mood' : 'Lorem ipsum dolor sit amet, consectetur adipiscing elit...' + }, + { + 'id' : '5725a680ae1ae9a3c960d487', + 'name' : 'Henderson', + 'avatar': 'assets/images/avatars/Henderson.jpg', + 'status': 'offline', + 'mood' : 'Lorem ipsum dolor sit amet, consectetur adipiscing elit...' + }, + { + 'id' : '5725a680b8d240c011dd224b', + 'name' : 'Josefina', + 'avatar': 'assets/images/avatars/Josefina.jpg', + 'status': 'online', + 'mood' : 'Lorem ipsum dolor sit amet, consectetur adipiscing elit...' + }, + { + 'id' : '5725a68034cb3968e1f79eac', + 'name' : 'Katina', + 'avatar': 'assets/images/avatars/Katina.jpg', + 'status': 'away', + 'mood' : 'Lorem ipsum dolor sit amet, consectetur adipiscing elit...' + }, + { + 'id' : '5725a6801146cce777df2a08', + 'name' : 'Lily', + 'avatar': 'assets/images/avatars/Lily.jpg', + 'status': 'do-not-disturb', + 'mood' : 'Lorem ipsum dolor sit amet, consectetur adipiscing elit...', + 'unread': '10' + }, + { + 'id' : '5725a6808a178bfd034d6ecf', + 'name' : 'Mai', + 'avatar': 'assets/images/avatars/Mai.jpg', + 'status': 'away', + 'mood' : 'Lorem ipsum dolor sit amet, consectetur adipiscing elit...' + }, + { + 'id' : '5725a680653c265f5c79b5a9', + 'name' : 'Nancy', + 'avatar': 'assets/images/avatars/Nancy.jpg', + 'status': 'do-not-disturb', + 'mood' : 'Lorem ipsum dolor sit amet, consectetur adipiscing elit...' + }, + { + 'id' : '5725a680bbcec3cc32a8488a', + 'name' : 'Nora', + 'avatar': 'assets/images/avatars/Nora.jpg', + 'status': 'do-not-disturb', + 'mood' : 'Lorem ipsum dolor sit amet, consectetur adipiscing elit...', + 'unread': '7' + }, + { + 'id' : '5725a6803d87f1b77e17b62b', + 'name' : 'Odessa', + 'avatar': 'assets/images/avatars/Odessa.jpg', + 'status': 'away', + 'mood' : 'Lorem ipsum dolor sit amet, consectetur adipiscing elit...', + 'unread': '1' + }, + { + 'id' : '5725a680e87cb319bd9bd673', + 'name' : 'Reyna', + 'avatar': 'assets/images/avatars/Reyna.jpg', + 'status': 'offline', + 'mood' : 'Lorem ipsum dolor sit amet, consectetur adipiscing elit...' + }, + { + 'id' : '5725a6802d10e277a0f35775', + 'name' : 'Shauna', + 'avatar': 'assets/images/avatars/Shauna.jpg', + 'status': 'online', + 'mood' : 'Lorem ipsum dolor sit amet, consectetur adipiscing elit...' + }, + { + 'id' : '5725a680aef1e5cf26dd3d1f', + 'name' : 'Shepard', + 'avatar': 'assets/images/avatars/Shepard.jpg', + 'status': 'online', + 'mood' : 'Lorem ipsum dolor sit amet, consectetur adipiscing elit...' + }, + { + 'id' : '5725a680cd7efa56a45aea5d', + 'name' : 'Tillman', + 'avatar': 'assets/images/avatars/Tillman.jpg', + 'status': 'do-not-disturb', + 'mood' : '', + 'unread': '99+' + }, + { + 'id' : '5725a680fb65c91a82cb35e2', + 'name' : 'Trevino', + 'avatar': 'assets/images/avatars/Trevino.jpg', + 'status': 'away', + 'mood' : 'Lorem ipsum dolor sit amet, consectetur adipiscing elit...' + }, + { + 'id' : '5725a68018c663044be49cbf', + 'name' : 'Tyson', + 'avatar': 'assets/images/avatars/Tyson.jpg', + 'status': 'do-not-disturb', + 'mood' : 'Lorem ipsum dolor sit amet, consectetur adipiscing elit...' + }, + { + 'id' : '5725a6809413bf8a0a5272b1', + 'name' : 'Velazquez', + 'avatar': 'assets/images/avatars/Velazquez.jpg', + 'status': 'online', + 'mood' : 'Lorem ipsum dolor sit amet, consectetur adipiscing elit...' + } + ]; + + public static chats = [ + { + 'id' : '1725a680b3249760ea21de52', + 'dialog': [ + { + 'who' : '5725a680b3249760ea21de52', + 'message': 'Quickly come to the meeting room 1B, we have a big server issue', + 'time' : '2017-03-22T08:54:28.299Z' + }, + { + 'who' : '5725a6802d10e277a0f35724', + 'message': 'I’m having breakfast right now, can’t you wait for 10 minutes?', + 'time' : '2017-03-22T08:55:28.299Z' + }, + { + 'who' : '5725a680b3249760ea21de52', + 'message': 'We are losing money! Quick!', + 'time' : '2017-03-22T09:00:28.299Z' + }, + { + 'who' : '5725a6802d10e277a0f35724', + 'message': 'It’s not my money, you know. I will eat my breakfast and then I will come to the meeting room.', + 'time' : '2017-03-22T09:02:28.299Z' + }, + { + 'who' : '5725a680b3249760ea21de52', + 'message': 'You are the worst!', + 'time' : '2017-03-22T09:05:28.299Z' + }, + { + 'who' : '5725a680b3249760ea21de52', + 'message': 'We are losing money! Quick!', + 'time' : '2017-03-22T09:15:28.299Z' + }, + { + 'who' : '5725a6802d10e277a0f35724', + 'message': 'It’s not my money, you know. I will eat my breakfast and then I will come to the meeting room.', + 'time' : '2017-03-22T09:20:28.299Z' + }, + { + 'who' : '5725a680b3249760ea21de52', + 'message': 'You are the worst!', + 'time' : '2017-03-22T09:22:28.299Z' + }, + { + 'who' : '5725a680b3249760ea21de52', + 'message': 'We are losing money! Quick!', + 'time' : '2017-03-22T09:25:28.299Z' + }, + { + 'who' : '5725a6802d10e277a0f35724', + 'message': 'It’s not my money, you know. I will eat my breakfast and then I will come to the meeting room.', + 'time' : '2017-03-22T09:27:28.299Z' + }, + { + 'who' : '5725a680b3249760ea21de52', + 'message': 'You are the worst!', + 'time' : '2017-03-22T09:33:28.299Z' + }, + { + 'who' : '5725a680b3249760ea21de52', + 'message': 'We are losing money! Quick!', + 'time' : '2017-03-22T09:35:28.299Z' + }, + { + 'who' : '5725a6802d10e277a0f35724', + 'message': 'It’s not my money, you know. I will eat my breakfast and then I will come to the meeting room.', + 'time' : '2017-03-22T09:45:28.299Z' + }, + { + 'who' : '5725a680b3249760ea21de52', + 'message': 'You are the worst!', + 'time' : '2017-03-22T10:00:28.299Z' + } + ] + }, + { + 'id' : '2725a680b8d240c011dd2243', + 'dialog': [ + { + 'who' : '5725a680b8d240c011dd224b', + 'message': 'Quickly come to the meeting room 1B, we have a big server issue', + 'time' : '2017-04-22T01:00:00.299Z' + }, + { + 'who' : '5725a6802d10e277a0f35724', + 'message': 'I’m having breakfast right now, can’t you wait for 10 minutes?', + 'time' : '2017-04-22T01:05:00.299Z' + }, + { + 'who' : '5725a680b8d240c011dd224b', + 'message': 'We are losing money! Quick!', + 'time' : '2017-04-22T01:10:00.299Z' + } + ] + }, + { + 'id' : '3725a6809413bf8a0a5272b4', + 'dialog': [ + { + 'who' : '5725a6809413bf8a0a5272b1', + 'message': 'Quickly come to the meeting room 1B, we have a big server issue', + 'time' : '2017-04-22T02:10:00.299Z' + } + ] + } + ]; + + public static user = [ + { + 'id' : '5725a6802d10e277a0f35724', + 'name' : 'John Doe', + 'avatar' : 'assets/images/avatars/profile.jpg', + 'status' : 'online', + 'mood' : '', + 'chatList': [ + { + 'chatId' : '1725a680b3249760ea21de52', + 'contactId' : '5725a680b3249760ea21de52', + 'lastMessageTime': '2017-06-12T02:10:18.931Z' + }, + { + 'chatId' : '2725a680b8d240c011dd2243', + 'contactId' : '5725a680606588342058356d', + 'lastMessageTime': '2017-02-18T10:30:18.931Z' + }, + { + 'chatId' : '3725a6809413bf8a0a5272b4', + 'contactId' : '5725a68009e20d0a9e9acf2a', + 'lastMessageTime': '2017-03-18T12:30:18.931Z' + } + ] + } + ]; + +} diff --git a/src/app/fake-db/fake-db.service.ts b/src/app/fake-db/fake-db.service.ts index 32027ba6..de47b4b6 100644 --- a/src/app/fake-db/fake-db.service.ts +++ b/src/app/fake-db/fake-db.service.ts @@ -17,6 +17,7 @@ import { SearchFakeDb } from 'app/fake-db/search'; import { FaqFakeDb } from 'app/fake-db/faq'; import { KnowledgeBaseFakeDb } from 'app/fake-db/knowledge-base'; import { IconsFakeDb } from 'app/fake-db/icons'; +import { ChatPanelFakeDb } from 'app/fake-db/chat-panel'; import { QuickPanelFakeDb } from 'app/fake-db/quick-panel'; export class FakeDbService implements InMemoryDbService @@ -89,6 +90,11 @@ export class FakeDbService implements InMemoryDbService // Icons 'icons': IconsFakeDb.icons, + // Chat Panel + 'chat-panel-contacts' : ChatPanelFakeDb.contacts, + 'chat-panel-chats': ChatPanelFakeDb.chats, + 'chat-panel-user': ChatPanelFakeDb.user, + // Quick Panel 'quick-panel-notes' : QuickPanelFakeDb.notes, 'quick-panel-events': QuickPanelFakeDb.events diff --git a/src/app/layout/components/chat-panel/chat-panel.component.html b/src/app/layout/components/chat-panel/chat-panel.component.html new file mode 100644 index 00000000..5bc074af --- /dev/null +++ b/src/app/layout/components/chat-panel/chat-panel.component.html @@ -0,0 +1,105 @@ +
+ + + +
+ account_circle +

Team Members

+
+ +
+ + + +
+ + +

{{contact.name}}

+
+ +
+ + + + + +
+ + + + + + + + + +

+ {{contact.name}} +

+ +
{{contact.unread}}
+
+ +
+ +
+ +
+ + + +
+ +
+ +
+ + + + + +
+
{{message.message}}
+
{{message.time | date:'medium'}}
+
+ +
+ +
+ +
+ +
+ + + + + + + +
+ +
+ +
+ +
\ No newline at end of file diff --git a/src/app/layout/components/chat-panel/chat-panel.component.scss b/src/app/layout/components/chat-panel/chat-panel.component.scss new file mode 100644 index 00000000..51f951c2 --- /dev/null +++ b/src/app/layout/components/chat-panel/chat-panel.component.scss @@ -0,0 +1,218 @@ +@import "src/@fuse/scss/fuse"; + +chat-panel { + display: flex; + flex-direction: column; + flex: 1 1 auto; + width: 280px; + min-width: 280px; + max-width: 280px; + z-index: 99; + overflow: hidden; + + .header { + height: 64px; + max-height: 64px; + min-height: 64px; + + .title { + + mat-icon { + margin-left: 4px; + } + + h3 { + max-width: 120px; + transition: opacity 300ms ease-in-out; + } + } + } + + #contacts-list { + padding: 8px 0; + overflow: auto; + + .mat-list-item { + cursor: pointer; + position: relative; + + &.offline { + + .mat-list-item-content { + + img { + filter: grayscale(100%); + opacity: 0.7; + } + + h3 { + opacity: 0.7; + } + } + } + + .mat-list-item-content { + + .unread-count { + position: absolute; + min-width: 18px; + height: 18px; + top: 4px; + left: 10px; + border-radius: 9px; + padding: 0 5px; + font-size: 11px; + text-align: center; + display: flex; + align-items: center; + justify-content: center; + background-color: mat-color(mat-palette($mat-indigo)); + color: white; + box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.35); + } + + .status-icon { + position: absolute; + width: 12px; + height: 12px; + bottom: 3px; + left: 44px; + border: 2px solid white; + border-radius: 50%; + + &.online { + background-color: #4CAF50; + } + + &.do-not-disturb { + background-color: #F44336; + } + + &.away { + background-color: #FFC107; + } + + &.offline { + background-color: #646464; + } + } + } + } + } + + #chat { + background-color: mat-color(mat-palette($mat-grey, 200)); + + .messages { + overflow: auto; + + .message-row { + padding: 16px; + + .bubble { + position: relative; + padding: 6px 7px 8px 9px; + background-color: #FFF; + box-shadow: 0 1px .5px rgba(0, 0, 0, .13); + border-radius: 6px; + + &:before { + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAmCAMAAADp2asXAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAADGUExURQAAAP////b29vn5+f///wAAAP///wAAAAAAAP///9ra2v////j4+PHx8fv7++Hh4fHx8f////////////////39/QAAAP////////z8/P////39/f39/fz8/P////////////z8/P////////////z8/P////////////v7+/Hx8f///9bW1vz8/K2trf////39/f39/WJiYgAAAExMTFtbWwAAAN3d3cjIyPr6+vX19QAAAO7u7vz8/NTU1Ofn5zMzM////zGPlXsAAABBdFJOUwAcm/kREh4CCDWL1SneR6TfAQffhMYK/A5nRrLWfRc5DW2ih5f+19Kn+9v4g/1LCJuXHwQUKgahcXS6DNnlDMMKKzPoTgAAAKBJREFUKM+V08USwmAQA+C/0NIWd3d3d8/7vxTMcIPkQK7f7CG7s8bQAOY/SCuwFYQU1P+eiCqIK2gpWCmoCrAgoKQgJ8CHgIqAMjg0MxxSQ3DogEMWFBZtUPAHYGB1CyDQWE6AH7BrfXzlAxGAQhECTGAmwN1Okz0Gb/LW4fEItIfrOfNELMh3tck7u+PhcT2zQ7l77/K8iY8yJwV3BeYFqpc/uSyPGdAAAAAASUVORK5CYII=); + content: ''; + position: absolute; + left: -11px; + bottom: 3px; + width: 12px; + height: 19px; + background-position: 50% 50%; + background-repeat: no-repeat; + background-size: contain; + } + + .message { + white-space: pre-wrap; + } + + .time { + font-size: 11px; + margin-top: 8px; + text-align: right; + } + } + + &.contact { + + .avatar { + margin: 0 16px 0 0; + } + } + + &.user { + align-items: flex-end; + + .avatar { + order: 2; + margin: 0 0 0 16px; + } + + .bubble { + margin-left: auto; + background-color: #E8F5E9; + border: 1px solid #DFEBE0; + order: 1; + &:before { + right: -11px; + left: auto; + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAmCAMAAADp2asXAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAD2UExURQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGRsXAAAANzwzNPmxNrtyau5oIWRedDkwNntyczgwdfpyJ+/n97wzsLWtNjsytvwzczfvtPmxau6nNjqxtrtyio1KtzwzNjryAAAANzwzgAAANzwzK7Aor/Us9Lnw8vevAAAAMzevtbpxrvMrX+IdwAAAEROOi45Lr3MrZGjf9LoxX+MctnqydLkwhgYGMzfv9vuyQAAANzwzNvuy9zxy7vMu7XGqNvtzKKykwAAANruzKq6nLnMriQkGMXXuL3PsNjsySgzKAAAANLkw83fvd3vy9z4xtzwzRpFmIEAAABQdFJOUwAXChEGBAMBAgwhDvJ7k0YqMc0Zmwj6apf2kjU0+dkw/swh/CP9j2Wr2gndvaYeBRoxQg6gUPt/FaHJGdTj9A9k7XQLeE6iFcN12xkSt9r4NKizowAAAMFJREFUKM+V0sdywlAMBVDbMX7PQCihQ+iQ0HsJvfem/P/PwBIzugu0PXNnNNJVyPmhsIPhhoB2COwIGuLdhAcl3AhCBoBoHUC6BCBbA0C/EkBFB5D/FjxQwQYg1RI8UKINgDoSAPUlAPqUAMgfAEBfXsEDBV0+Hogi4Zhg4THj9YwHoqEBYOrgYTI3GVgMNn8r+Qq94k9yZNosW/3Hy9VuTjWfHkOX6367bGZUU7de66ieHZrO1OGg8Z1WTgYAFLgD5S1PCkzo1B0AAAAASUVORK5CYII=); + } + } + } + } + } + + .reply-form { + position: relative; + @include mat-elevation(8); + } + } +} + +fuse-sidebar { + + &.chat-panel { + + // Folded + &.folded { + + chat-panel { + + .header { + + .title { + + h3 { + opacity: 0; + } + } + } + } + + // Folded unfolded + &.unfolded { + + chat-panel { + + .header { + + .title { + + h3 { + opacity: 1; + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/src/app/layout/components/chat-panel/chat-panel.component.ts b/src/app/layout/components/chat-panel/chat-panel.component.ts new file mode 100644 index 00000000..e3a6cfe8 --- /dev/null +++ b/src/app/layout/components/chat-panel/chat-panel.component.ts @@ -0,0 +1,217 @@ +import { Component, ElementRef, OnDestroy, OnInit, ViewChild, ViewEncapsulation } from '@angular/core'; +import { NgForm } from '@angular/forms'; +import { HttpClient } from '@angular/common/http'; + +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; + +import { FuseSidebarService } from '@fuse/components/sidebar/sidebar.service'; +import { FusePerfectScrollbarDirective } from '@fuse/directives/fuse-perfect-scrollbar/fuse-perfect-scrollbar.directive'; +import { ChatPanelService } from 'app/layout/components/chat-panel/chat-panel.service'; + +@Component({ + selector : 'chat-panel', + templateUrl : './chat-panel.component.html', + styleUrls : ['./chat-panel.component.scss'], + encapsulation: ViewEncapsulation.None +}) +export class ChatPanelComponent implements OnInit, OnDestroy +{ + contacts: any[]; + contact: any; + chat: any; + sidebarFolded: boolean; + user: any; + view: string; + + @ViewChild(FusePerfectScrollbarDirective) + set fusePerfectScrollbarDirective(content: FusePerfectScrollbarDirective) + { + this._fusePerfectScrollbarDirective = content; + } + + @ViewChild('replyForm') + set replyForm(content: NgForm) + { + this._replyForm = content; + } + + @ViewChild('replyInput') + set replyInput(content: ElementRef) + { + this._replyInput = content; + } + + // Private + private _fusePerfectScrollbarDirective: FusePerfectScrollbarDirective; + private _replyForm: NgForm; + private _replyInput: ElementRef; + private _unsubscribeAll: Subject; + + /** + * Constructor + * + * @param {ChatPanelService} _chatPanelService + * @param {HttpClient} _httpClient + * @param {FuseSidebarService} _fuseSidebarService + */ + constructor( + private _chatPanelService: ChatPanelService, + private _httpClient: HttpClient, + private _fuseSidebarService: FuseSidebarService + ) + { + // Set the defaults + this.contact = null; + this.sidebarFolded = true; + this.view = 'contacts'; + + // Set the private defaults + this._unsubscribeAll = new Subject(); + } + + // ----------------------------------------------------------------------------------------------------- + // @ Lifecycle hooks + // ----------------------------------------------------------------------------------------------------- + + /** + * On init + */ + ngOnInit(): void + { + // Load the contacts + this._chatPanelService.loadContacts().then(() => { + + this.contacts = this._chatPanelService.contacts; + this.user = this._chatPanelService.user; + }); + + // Subscribe to the foldedChanged observable + this._fuseSidebarService.getSidebar('chatPanel').foldedChanged + .pipe(takeUntil(this._unsubscribeAll)) + .subscribe((folded) => { + this.sidebarFolded = folded; + }); + } + + /** + * On destroy + */ + ngOnDestroy(): void + { + // Unsubscribe from all subscriptions + this._unsubscribeAll.next(); + this._unsubscribeAll.complete(); + } + + // ----------------------------------------------------------------------------------------------------- + // @ Private methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Prepare the chat for the replies + */ + private _prepareChatForReplies(): void + { + setTimeout(() => { + + // Reset the reply form + this._replyForm.reset(); + + // Focus to the reply input + this._replyInput.nativeElement.focus(); + + // Scroll to the bottom of the messages list + if ( this._fusePerfectScrollbarDirective ) + { + this._fusePerfectScrollbarDirective.update(); + + setTimeout(() => { + this._fusePerfectScrollbarDirective.scrollToBottom(0); + }); + } + }); + } + + // ----------------------------------------------------------------------------------------------------- + // @ Public methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Toggle sidebar folded status + */ + toggleSidebarFolded(): void + { + this._fuseSidebarService.getSidebar('chatPanel').toggleFold(); + } + + /** + * Toggle sidebar opened status + */ + toggleSidebarOpen(): void + { + this._fuseSidebarService.getSidebar('chatPanel').toggleOpen(); + } + + /** + * Go to chat with the contact + * + * @param contact + */ + goToChat(contact): void + { + // Change the view + this.view = 'chat'; + + // Set the current contact + this.contact = contact; + + // Load the chat + this._chatPanelService.getChat(contact.id).then((chat) => { + + // Set the chat + this.chat = chat; + + // Prepare the chat for the replies + this._prepareChatForReplies(); + }); + } + + /** + * Go to contact view + */ + goToContacts(): void + { + // Change the view + this.view = 'contacts'; + + // Set the current contact as null + this.contact = null; + + // Clear the chat data + this.chat = null; + } + + /** + * Reply + */ + reply(): void + { + // Message + const message = { + who : this.user.id, + message: this._replyForm.form.value.message, + time : new Date().toISOString() + }; + + // Add the message to the chat + this.chat.dialog.push(message); + + // Update the server + this._chatPanelService.updateChat(this.chat.id, this.chat.dialog).then(response => { + + // Prepare the chat for the replies + this._prepareChatForReplies(); + }); + } +} diff --git a/src/app/layout/components/chat-panel/chat-panel.module.ts b/src/app/layout/components/chat-panel/chat-panel.module.ts new file mode 100644 index 00000000..8963e64d --- /dev/null +++ b/src/app/layout/components/chat-panel/chat-panel.module.ts @@ -0,0 +1,33 @@ +import { NgModule } from '@angular/core'; +import { MatButtonModule, MatFormFieldModule, MatIconModule, MatInputModule, MatListModule, MatRippleModule, MatTabsModule } from '@angular/material'; + +import { FuseSharedModule } from '@fuse/shared.module'; + +import { ChatPanelComponent } from 'app/layout/components/chat-panel/chat-panel.component'; +import { ChatPanelService } from 'app/layout/components/chat-panel/chat-panel.service'; + +@NgModule({ + declarations: [ + ChatPanelComponent + ], + providers : [ + ChatPanelService + ], + imports : [ + MatButtonModule, + MatFormFieldModule, + MatIconModule, + MatInputModule, + MatListModule, + MatTabsModule, + MatRippleModule, + + FuseSharedModule + ], + exports : [ + ChatPanelComponent + ] +}) +export class ChatPanelModule +{ +} diff --git a/src/app/layout/components/chat-panel/chat-panel.service.ts b/src/app/layout/components/chat-panel/chat-panel.service.ts new file mode 100644 index 00000000..8aa1b952 --- /dev/null +++ b/src/app/layout/components/chat-panel/chat-panel.service.ts @@ -0,0 +1,182 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; + +import { FuseUtils } from '@fuse/utils'; + +@Injectable() +export class ChatPanelService +{ + contacts: any[]; + chats: any[]; + user: any; + + /** + * Constructor + * + * @param {HttpClient} _httpClient + */ + constructor( + private _httpClient: HttpClient + ) + { + } + + /** + * Loader + * + * @returns {Promise | any} + */ + loadContacts(): Promise | any + { + return new Promise((resolve, reject) => { + Promise.all([ + this.getContacts(), + this.getUser() + ]).then( + ([contacts, user]) => { + this.contacts = contacts; + this.user = user; + resolve(); + }, + reject + ); + }); + } + + /** + * Get chat + * + * @param contactId + * @returns {Promise} + */ + getChat(contactId): Promise + { + const chatItem = this.user.chatList.find((item) => { + return item.contactId === contactId; + }); + + // Get the chat + return new Promise((resolve, reject) => { + + // If there is a chat with this user, return that. + if ( chatItem ) + { + this._httpClient.get('api/chat-panel-chats/' + chatItem.chatId) + .subscribe((chat) => { + + // Resolve the promise + resolve(chat); + + }, reject); + } + // If there is no chat with this user, create one... + else + { + this.createNewChat(contactId).then(() => { + + // and then recall the getChat method + this.getChat(contactId).then((chat) => { + resolve(chat); + }); + }); + } + }); + } + + /** + * Create new chat + * + * @param contactId + * @returns {Promise} + */ + createNewChat(contactId): Promise + { + return new Promise((resolve, reject) => { + + // Generate a new id + const chatId = FuseUtils.generateGUID(); + + // Prepare the chat object + const chat = { + id : chatId, + dialog: [] + }; + + // Prepare the chat list entry + const chatListItem = { + chatId : chatId, + contactId : contactId, + lastMessageTime: '2017-02-18T10:30:18.931Z' + }; + + // Add new chat list item to the user's chat list + this.user.chatList.push(chatListItem); + + // Post the created chat to the server + this._httpClient.post('api/chat-panel-chats', {...chat}) + .subscribe(() => { + + // Post the updated user data to the server + this._httpClient.post('api/chat-panel-user/' + this.user.id, this.user) + .subscribe(() => { + + // Resolve the promise + resolve(); + }); + }, reject); + }); + } + + /** + * Update the chat + * + * @param chatId + * @param dialog + * @returns {Promise} + */ + updateChat(chatId, dialog): Promise + { + return new Promise((resolve, reject) => { + + const newData = { + id : chatId, + dialog: dialog + }; + + this._httpClient.post('api/chat-panel-chats/' + chatId, newData) + .subscribe(updatedChat => { + resolve(updatedChat); + }, reject); + }); + } + + /** + * Get contacts + * + * @returns {Promise} + */ + getContacts(): Promise + { + return new Promise((resolve, reject) => { + this._httpClient.get('api/chat-panel-contacts') + .subscribe((response: any) => { + resolve(response); + }, reject); + }); + } + + /** + * Get user + * + * @returns {Promise} + */ + getUser(): Promise + { + return new Promise((resolve, reject) => { + this._httpClient.get('api/chat-panel-user') + .subscribe((response: any) => { + resolve(response[0]); + }, reject); + }); + } +} diff --git a/src/app/layout/components/toolbar/toolbar.component.html b/src/app/layout/components/toolbar/toolbar.component.html index 7ac75bcf..1607df5e 100644 --- a/src/app/layout/components/toolbar/toolbar.component.html +++ b/src/app/layout/components/toolbar/toolbar.component.html @@ -30,7 +30,7 @@ + +
+