diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 4a69a6e5..6ab85110 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -20,6 +20,7 @@ import { PerfectScrollbarConfigInterface, PerfectScrollbarModule } from 'ngx-per import { HttpModule } from '@angular/http'; import { InMemoryWebApiModule } from 'angular-in-memory-web-api'; import { FuseFakeDbService } from './fuse-fake-db/fuse-fake-db.service'; +import { INTERNAL_BROWSER_DYNAMIC_PLATFORM_PROVIDERS } from '@angular/platform-browser-dynamic/src/platform_providers'; const PERFECT_SCROLLBAR_CONFIG: PerfectScrollbarConfigInterface = { suppressScrollX: true @@ -30,6 +31,10 @@ const appRoutes: Routes = [ path : 'apps/mail', loadChildren: './main/apps/mail/mail.module#MailModule' }, + { + path : 'apps/chat', + loadChildren: './main/apps/chat/chat.module#ChatModule' + }, { path : '**', redirectTo: 'apps/dashboards/project' diff --git a/src/app/core/animations.ts b/src/app/core/animations.ts index ebb2429b..f140d216 100644 --- a/src/app/core/animations.ts +++ b/src/app/core/animations.ts @@ -1,4 +1,4 @@ -import {trigger, state, transition, animate, style} from '@angular/animations'; +import { trigger, state, transition, animate, style } from '@angular/animations'; export class Animations { @@ -8,4 +8,16 @@ export class Animations transition('1 => 0', animate('300ms ease-out')), transition('0 => 1', animate('300ms ease-in')) ]); + public static slideInLeft = trigger('slideInLeft', [ + state('void', style({transform: 'translateX(-100%)', display: 'none'})), + state('*', style({transform: 'translateX(0)', display: 'flex'})), + transition('void => *', animate('300ms')), + transition('* => void', animate('300ms')) + ]); + public static slideInRight = trigger('slideInRight', [ + state('void', style({transform: 'translateX(100%)', display: 'none'})), + state('*', style({transform: 'translateX(0)', display: 'flex'})), + transition('void => *', animate('300ms')), + transition('* => void', animate('300ms')) + ]); } diff --git a/src/app/core/modules/shared.module.ts b/src/app/core/modules/shared.module.ts index 66d60ba4..2b19e199 100644 --- a/src/app/core/modules/shared.module.ts +++ b/src/app/core/modules/shared.module.ts @@ -1,5 +1,5 @@ import { NgModule } from '@angular/core'; -import { FormsModule } from '@angular/forms'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { CommonModule } from '@angular/common'; import { MaterialModule } from './material.module'; @@ -23,7 +23,8 @@ import { FusePipesModule } from '../pipes/pipes.module'; CommonModule, FormsModule, FusePipesModule, - PerfectScrollbarModule + PerfectScrollbarModule, + ReactiveFormsModule ], exports : [ FlexLayoutModule, @@ -33,7 +34,8 @@ import { FusePipesModule } from '../pipes/pipes.module'; FuseMdSidenavHelperDirective, FuseMdSidenavTogglerDirective, FusePipesModule, - PerfectScrollbarModule + PerfectScrollbarModule, + ReactiveFormsModule ] }) diff --git a/src/app/core/pipes/filter.pipe.ts b/src/app/core/pipes/filter.pipe.ts new file mode 100644 index 00000000..bc8835b0 --- /dev/null +++ b/src/app/core/pipes/filter.pipe.ts @@ -0,0 +1,152 @@ +/** + * Created by vadimdez on 28/06/16. + */ +import { Pipe, Injectable } from '@angular/core'; + +@Pipe({ + name: 'filterBy', + pure: false +}) + +@Injectable() +export class FilterPipe { + + private filterByString(filter) { + if (filter) { + filter = filter.toLowerCase(); + } + return value => { + return !filter || (value ? ('' + value).toLowerCase().indexOf(filter) !== -1 : false); + } + } + + private filterByBoolean(filter) { + return value => { + return Boolean(value) === filter; + } + } + + private filterByObject(filter) { + return value => { + for (let key in filter) { + + if (key === '$or') { + if (!this.filterByOr(filter.$or)(this.getValue(value))) { + return false; + } + continue; + } + + if (!value.hasOwnProperty(key) && !Object.getOwnPropertyDescriptor(Object.getPrototypeOf(value), key)) { + return false; + } + + let val = this.getValue(value[key]); + const filterType = typeof filter[key]; + let isMatching; + + if (filterType === 'boolean') { + isMatching = this.filterByBoolean(filter[key])(val); + } else if (filterType === 'string') { + isMatching = this.filterByString(filter[key])(val); + } else if (filterType === 'object') { + isMatching = this.filterByObject(filter[key])(val); + } else { + isMatching = this.filterDefault(filter[key])(val); + } + + if (!isMatching) { + return false; + } + } + + return true; + } + } + + /** + * Filter value by $or + * + * @param filter + * @returns {(value:any)=>boolean} + */ + private filterByOr(filter: any[]) { + return (value: any) => { + let hasMatch = false; + const length = filter.length; + const isArray = value instanceof Array; + + const arrayComparison = (i) => { + return value.indexOf(filter[i]) !== -1; + }; + const otherComparison = (i) => { + return value === filter[i]; + }; + const comparison = isArray ? arrayComparison : otherComparison; + + for (let i = 0; i < length; i++) { + if (comparison(i)) { + hasMatch = true; + break; + } + } + + return hasMatch; + }; + } + + /** + * Checks function's value if type is function otherwise same value + * @param value + * @returns {any} + */ + private getValue(value: any) { + return typeof value === 'function' ? value() : value; + } + + /** + * Defatul filterDefault function + * + * @param filter + * @returns {(value:any)=>boolean} + */ + private filterDefault(filter) { + return value => { + return filter === undefined || filter == value; + } + } + + private isNumber(value) { + return !isNaN(parseInt(value, 10)) && isFinite(value); + } + + transform(array: any[], filter: any): any { + const type = typeof filter; + + if (!array) { + return array; + } + + if (type === 'boolean') { + return array.filter(this.filterByBoolean(filter)); + } + + if (type === 'string') { + if (this.isNumber(filter)) { + return array.filter(this.filterDefault(filter)); + } + + return array.filter(this.filterByString(filter)); + } + + if (type === 'object') { + return array.filter(this.filterByObject(filter)); + } + + if (type === 'function') { + return array.filter(filter); + } + + return array.filter(this.filterDefault(filter)); + } +} diff --git a/src/app/core/pipes/pipes.module.ts b/src/app/core/pipes/pipes.module.ts index a5f01259..0a858c00 100644 --- a/src/app/core/pipes/pipes.module.ts +++ b/src/app/core/pipes/pipes.module.ts @@ -4,20 +4,24 @@ import { KeysPipe } from './keys.pipe'; import { GetByIdPipe } from './getById.pipe'; import { HtmlToPlaintextPipe } from './htmlToPlaintext.pipe'; import { SearchPipe } from './search.pipe'; +import { FilterPipe } from './filter.pipe'; @NgModule({ declarations: [ KeysPipe, GetByIdPipe, HtmlToPlaintextPipe, - SearchPipe + SearchPipe, + FilterPipe + ], imports : [], exports : [ KeysPipe, GetByIdPipe, HtmlToPlaintextPipe, - SearchPipe + SearchPipe, + FilterPipe ] }) diff --git a/src/app/core/scss/core.scss b/src/app/core/scss/core.scss index 3d09f236..7ca3040c 100644 --- a/src/app/core/scss/core.scss +++ b/src/app/core/scss/core.scss @@ -20,12 +20,16 @@ @include angular-material-typography($custom-typography); // Partials +@import "partials/normalize"; +@import "partials/spacing"; @import "partials/global"; -@import "partials/_material"; +@import "partials/icons"; +@import "partials/material"; @import "partials/angular-material-fix"; @import "partials/typography"; @import "partials/page-layouts"; @import "partials/navigation"; +@import "partials/forms"; // Plugins @import "partials/plugins/plugins"; diff --git a/src/app/core/scss/partials/_angular-material-fix.scss b/src/app/core/scss/partials/_angular-material-fix.scss index 8ddd8f91..f0ec4593 100644 --- a/src/app/core/scss/partials/_angular-material-fix.scss +++ b/src/app/core/scss/partials/_angular-material-fix.scss @@ -3,4 +3,4 @@ .mat-button-ripple { border-radius: 50%; } -} \ No newline at end of file +} diff --git a/src/app/core/scss/partials/_forms.scss b/src/app/core/scss/partials/_forms.scss new file mode 100644 index 00000000..2583493e --- /dev/null +++ b/src/app/core/scss/partials/_forms.scss @@ -0,0 +1,15 @@ +button, +input[type=email], +input[type=tel], +input[type=text], +input[type=password], +input[type=image], +input[type=submit], +input[type=button], +input[type=search], +textarea { + appearance: none; + -moz-appearance: none; + -webkit-appearance: none; + outline: none; +} diff --git a/src/app/core/scss/partials/_global.scss b/src/app/core/scss/partials/_global.scss index 7f51a123..1426778e 100644 --- a/src/app/core/scss/partials/_global.scss +++ b/src/app/core/scss/partials/_global.scss @@ -2,6 +2,10 @@ box-sizing: border-box; } +html { + +} + html, body { margin: 0; width: 100%; @@ -21,4 +25,4 @@ body { > md-sidenav-container { height: 100%; } -} \ No newline at end of file +} diff --git a/src/app/core/scss/partials/_icons.scss b/src/app/core/scss/partials/_icons.scss new file mode 100644 index 00000000..d64fd37f --- /dev/null +++ b/src/app/core/scss/partials/_icons.scss @@ -0,0 +1,22 @@ +i, +md-icon { + color: rgba(0, 0, 0, 0.54); + font-size: 24px; + width: 24px; + height: 24px; + min-width: 24px; + min-height: 24px; + line-height: 24px; + + @for $size from 2 through 128 { + + &.s-#{$size * 2} { + font-size: #{($size * 2) + 'px'} !important; + width: #{($size * 2) + 'px'} !important; + height: #{($size * 2) + 'px'} !important; + min-width: #{($size * 2) + 'px'} !important; + min-height: #{($size * 2) + 'px'} !important; + line-height: #{($size * 2) + 'px'} !important; + } + } +} diff --git a/src/app/core/scss/partials/_material.scss b/src/app/core/scss/partials/_material.scss index 3ec5e82e..406d23f8 100644 --- a/src/app/core/scss/partials/_material.scss +++ b/src/app/core/scss/partials/_material.scss @@ -39,6 +39,54 @@ } } +.avatar-wrapper { + position: relative; + + .avatar { + margin-top: 0; + margin-bottom: 0; + } + md-icon.status { + position: absolute; + top: 28px; + left: 28px; + } +} + +md-icon.status { + border-radius: 50%; + + &.online { + color: #4CAF50; + &:before { + content: "check_circle"; + } + } + + &.do-not-disturb { + color: #F44336; + &:before { + content: "do_not_disturb_on"; + } + } + + &.away { + background-color: #FFC107; + color: #FFFFFF; + &:before { + content: "access_time"; + } + } + + &.offline { + color: #646464; + background-color: #FFFFFF; + &:before { + content: "not_interested"; + } + } +} + /*----------------------------------------------------------------*/ /* Forms /*----------------------------------------------------------------*/ diff --git a/src/app/core/scss/partials/_normalize.scss b/src/app/core/scss/partials/_normalize.scss new file mode 100644 index 00000000..6d95002c --- /dev/null +++ b/src/app/core/scss/partials/_normalize.scss @@ -0,0 +1,447 @@ +/*! normalize.css v7.0.0 | MIT License | github.com/necolas/normalize.css */ + +/* Document + ========================================================================== */ + +/** + * 1. Correct the line height in all browsers. + * 2. Prevent adjustments of font size after orientation changes in + * IE on Windows Phone and in iOS. + */ + +html { + line-height: 1.15; /* 1 */ + -ms-text-size-adjust: 100%; /* 2 */ + -webkit-text-size-adjust: 100%; /* 2 */ +} + +/* Sections + ========================================================================== */ + +/** + * Remove the margin in all browsers (opinionated). + */ + +body { + margin: 0; +} + +/** + * Add the correct display in IE 9-. + */ + +article, +aside, +footer, +header, +nav, +section { + display: block; +} + +/** + * Correct the font size and margin on `h1` elements within `section` and + * `article` contexts in Chrome, Firefox, and Safari. + */ + +h1 { + font-size: 2em; + margin: 0.67em 0; +} + +/* Grouping content + ========================================================================== */ + +/** + * Add the correct display in IE 9-. + * 1. Add the correct display in IE. + */ + +figcaption, +figure, +main { /* 1 */ + display: block; +} + +/** + * Add the correct margin in IE 8. + */ + +figure { + margin: 1em 40px; +} + +/** + * 1. Add the correct box sizing in Firefox. + * 2. Show the overflow in Edge and IE. + */ + +hr { + box-sizing: content-box; /* 1 */ + height: 0; /* 1 */ + overflow: visible; /* 2 */ +} + +/** + * 1. Correct the inheritance and scaling of font size in all browsers. + * 2. Correct the odd `em` font sizing in all browsers. + */ + +pre { + font-family: monospace, monospace; /* 1 */ + font-size: 1em; /* 2 */ +} + +/* Text-level semantics + ========================================================================== */ + +/** + * 1. Remove the gray background on active links in IE 10. + * 2. Remove gaps in links underline in iOS 8+ and Safari 8+. + */ + +a { + background-color: transparent; /* 1 */ + -webkit-text-decoration-skip: objects; /* 2 */ +} + +/** + * 1. Remove the bottom border in Chrome 57- and Firefox 39-. + * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. + */ + +abbr[title] { + border-bottom: none; /* 1 */ + text-decoration: underline; /* 2 */ + text-decoration: underline dotted; /* 2 */ +} + +/** + * Prevent the duplicate application of `bolder` by the next rule in Safari 6. + */ + +b, +strong { + font-weight: inherit; +} + +/** + * Add the correct font weight in Chrome, Edge, and Safari. + */ + +b, +strong { + font-weight: bolder; +} + +/** + * 1. Correct the inheritance and scaling of font size in all browsers. + * 2. Correct the odd `em` font sizing in all browsers. + */ + +code, +kbd, +samp { + font-family: monospace, monospace; /* 1 */ + font-size: 1em; /* 2 */ +} + +/** + * Add the correct font style in Android 4.3-. + */ + +dfn { + font-style: italic; +} + +/** + * Add the correct background and color in IE 9-. + */ + +mark { + background-color: #ff0; + color: #000; +} + +/** + * Add the correct font size in all browsers. + */ + +small { + font-size: 80%; +} + +/** + * Prevent `sub` and `sup` elements from affecting the line height in + * all browsers. + */ + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sub { + bottom: -0.25em; +} + +sup { + top: -0.5em; +} + +/* Embedded content + ========================================================================== */ + +/** + * Add the correct display in IE 9-. + */ + +audio, +video { + display: inline-block; +} + +/** + * Add the correct display in iOS 4-7. + */ + +audio:not([controls]) { + display: none; + height: 0; +} + +/** + * Remove the border on images inside links in IE 10-. + */ + +img { + border-style: none; +} + +/** + * Hide the overflow in IE. + */ + +svg:not(:root) { + overflow: hidden; +} + +/* Forms + ========================================================================== */ + +/** + * 1. Change the font styles in all browsers (opinionated). + * 2. Remove the margin in Firefox and Safari. + */ + +button, +input, +optgroup, +select, +textarea { + font-family: sans-serif; /* 1 */ + font-size: 100%; /* 1 */ + line-height: 1.15; /* 1 */ + margin: 0; /* 2 */ +} + +/** + * Show the overflow in IE. + * 1. Show the overflow in Edge. + */ + +button, +input { /* 1 */ + overflow: visible; +} + +/** + * Remove the inheritance of text transform in Edge, Firefox, and IE. + * 1. Remove the inheritance of text transform in Firefox. + */ + +button, +select { /* 1 */ + text-transform: none; +} + +/** + * 1. Prevent a WebKit bug where (2) destroys native `audio` and `video` + * controls in Android 4. + * 2. Correct the inability to style clickable types in iOS and Safari. + */ + +button, +html [type="button"], /* 1 */ +[type="reset"], +[type="submit"] { + -webkit-appearance: button; /* 2 */ +} + +/** + * Remove the inner border and padding in Firefox. + */ + +button::-moz-focus-inner, +[type="button"]::-moz-focus-inner, +[type="reset"]::-moz-focus-inner, +[type="submit"]::-moz-focus-inner { + border-style: none; + padding: 0; +} + +/** + * Restore the focus styles unset by the previous rule. + */ + +button:-moz-focusring, +[type="button"]:-moz-focusring, +[type="reset"]:-moz-focusring, +[type="submit"]:-moz-focusring { + outline: 1px dotted ButtonText; +} + +/** + * Correct the padding in Firefox. + */ + +fieldset { + padding: 0.35em 0.75em 0.625em; +} + +/** + * 1. Correct the text wrapping in Edge and IE. + * 2. Correct the color inheritance from `fieldset` elements in IE. + * 3. Remove the padding so developers are not caught out when they zero out + * `fieldset` elements in all browsers. + */ + +legend { + box-sizing: border-box; /* 1 */ + color: inherit; /* 2 */ + display: table; /* 1 */ + max-width: 100%; /* 1 */ + padding: 0; /* 3 */ + white-space: normal; /* 1 */ +} + +/** + * 1. Add the correct display in IE 9-. + * 2. Add the correct vertical alignment in Chrome, Firefox, and Opera. + */ + +progress { + display: inline-block; /* 1 */ + vertical-align: baseline; /* 2 */ +} + +/** + * Remove the default vertical scrollbar in IE. + */ + +textarea { + overflow: auto; +} + +/** + * 1. Add the correct box sizing in IE 10-. + * 2. Remove the padding in IE 10-. + */ + +[type="checkbox"], +[type="radio"] { + box-sizing: border-box; /* 1 */ + padding: 0; /* 2 */ +} + +/** + * Correct the cursor style of increment and decrement buttons in Chrome. + */ + +[type="number"]::-webkit-inner-spin-button, +[type="number"]::-webkit-outer-spin-button { + height: auto; +} + +/** + * 1. Correct the odd appearance in Chrome and Safari. + * 2. Correct the outline style in Safari. + */ + +[type="search"] { + -webkit-appearance: textfield; /* 1 */ + outline-offset: -2px; /* 2 */ +} + +/** + * Remove the inner padding and cancel buttons in Chrome and Safari on macOS. + */ + +[type="search"]::-webkit-search-cancel-button, +[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} + +/** + * 1. Correct the inability to style clickable types in iOS and Safari. + * 2. Change font properties to `inherit` in Safari. + */ + +::-webkit-file-upload-button { + -webkit-appearance: button; /* 1 */ + font: inherit; /* 2 */ +} + +/* Interactive + ========================================================================== */ + +/* + * Add the correct display in IE 9-. + * 1. Add the correct display in Edge, IE, and Firefox. + */ + +details, /* 1 */ +menu { + display: block; +} + +/* + * Add the correct display in all browsers. + */ + +summary { + display: list-item; +} + +/* Scripting + ========================================================================== */ + +/** + * Add the correct display in IE 9-. + */ + +canvas { + display: inline-block; +} + +/** + * Add the correct display in IE. + */ + +template { + display: none; +} + +/* Hidden + ========================================================================== */ + +/** + * Add the correct display in IE 10-. + */ + +[hidden] { + display: none; +} diff --git a/src/app/core/scss/partials/_spacing.scss b/src/app/core/scss/partials/_spacing.scss new file mode 100644 index 00000000..6da1c3ef --- /dev/null +++ b/src/app/core/scss/partials/_spacing.scss @@ -0,0 +1,62 @@ +// Margin and Padding +@each $prop, $abbrev in (margin: m, padding: p) { + + @for $index from 0 through 64 { + $size: $index *4; + $length: #{$size}px; + .#{$abbrev}-#{$size} { + #{$prop}: $length !important; + } + .#{$abbrev}t-#{$size} { + #{$prop}-top: $length !important; + } + .#{$abbrev}r-#{$size} { + #{$prop}-right: $length !important; + } + .#{$abbrev}b-#{$size} { + #{$prop}-bottom: $length !important; + } + .#{$abbrev}l-#{$size} { + #{$prop}-left: $length !important; + } + .#{$abbrev}x-#{$size} { + #{$prop}-right: $length !important; + #{$prop}-left: $length !important; + } + .#{$abbrev}y-#{$size} { + #{$prop}-top: $length !important; + #{$prop}-bottom: $length !important; + } + } +} + +// Some special margin utils +.m-auto { + margin: auto !important; +} + +.mt-auto { + margin-top: auto !important; +} + +.mr-auto { + margin-right: auto !important; +} + +.mb-auto { + margin-bottom: auto !important; +} + +.ml-auto { + margin-left: auto !important; +} + +.mx-auto { + margin-right: auto !important; + margin-left: auto !important; +} + +.my-auto { + margin-top: auto !important; + margin-bottom: auto !important; +} diff --git a/src/app/fuse-fake-db/chat.ts b/src/app/fuse-fake-db/chat.ts new file mode 100644 index 00000000..62771c02 --- /dev/null +++ b/src/app/fuse-fake-db/chat.ts @@ -0,0 +1,325 @@ +export class ChatFakeDb +{ + public static contacts = [ + { + 'id' : '5725a680b3249760ea21de52', + 'name' : 'Alice Freeman', + 'avatar': 'assets/images/avatars/alice.jpg', + 'status': 'online', + 'mood' : 'I never sign anything until I pretend to read it first..' + }, + { + 'id' : '5725a680606588342058356d', + 'name' : 'Arnold', + 'avatar': 'assets/images/avatars/Arnold.jpg', + 'status': 'do-not-disturb', + 'mood' : 'Looks like Andrew Jackson\'s been tossed to the back of the bus.' + }, + { + 'id' : '5725a68009e20d0a9e9acf2a', + 'name' : 'Barrera', + 'avatar': 'assets/images/avatars/Barrera.jpg', + 'status': 'do-not-disturb', + 'mood' : 'Love is going to bed early.Marriage is going to sleep early.', + 'unread': null + }, + { + 'id' : '5725a6809fdd915739187ed5', + 'name' : 'Blair', + 'avatar': 'assets/images/avatars/Blair.jpg', + 'status': 'offline', + 'mood' : 'I would be unstoppable. If i could just get started.', + 'unread': 3 + }, + { + 'id' : '5725a68007920cf75051da64', + 'name' : 'Boyle', + 'avatar': 'assets/images/avatars/Boyle.jpg', + 'status': 'offline', + 'mood' : '\'GOOD MORNING COFFEE\'....Meet your maker!!!!' + }, + { + 'id' : '5725a68031fdbb1db2c1af47', + 'name' : 'Christy', + 'avatar': 'assets/images/avatars/Christy.jpg', + 'status': 'offline', + 'mood' : 'We always hold hands. If I let go, she shops.', + }, + { + 'id' : '5725a680bc670af746c435e2', + 'name' : 'Copeland', + 'avatar': 'assets/images/avatars/Copeland.jpg', + 'status': 'online', + 'mood' : 'I get enough exercise just pushing my luck.', + }, + { + 'id' : '5725a680e7eb988a58ddf303', + 'name' : 'Estes', + 'avatar': 'assets/images/avatars/Estes.jpg', + 'status': 'away', + 'mood' : 'What comes after the man bun hairstyle? The he-hive!', + }, + { + 'id' : '5725a680dcb077889f758961', + 'name' : 'Harper', + 'avatar': 'assets/images/avatars/Harper.jpg', + 'status': 'offline', + 'mood' : 'Always try to be modest and be proud of it!', + }, + { + 'id' : '5725a6806acf030f9341e925', + 'name' : 'Helen', + 'avatar': 'assets/images/avatars/Helen.jpg', + 'status': 'away', + 'mood' : 'Why are there stitch marks on zombies? Who\'s giving them medical attention?', + }, + { + 'id' : '5725a680ae1ae9a3c960d487', + 'name' : 'Henderson', + 'avatar': 'assets/images/avatars/Henderson.jpg', + 'status': 'offline', + 'mood' : 'I can\'t decide if people who wear pajamas in public have given up on life or are living it to the fullest.', + }, + { + 'id' : '5725a680b8d240c011dd224b', + 'name' : 'Josefina', + 'avatar': 'assets/images/avatars/Josefina.jpg', + 'status': 'online', + 'mood' : 'The fastest way to being happy is to make other people happy. You go first', + }, + { + 'id' : '5725a68034cb3968e1f79eac', + 'name' : 'Katina', + 'avatar': 'assets/images/avatars/Katina.jpg', + 'status': 'away', + 'mood' : 'If I was a rat,,, I wouldn\'t give anyone my ass.', + }, + { + 'id' : '5725a6801146cce777df2a08', + 'name' : 'Lily', + 'avatar': 'assets/images/avatars/Lily.jpg', + 'status': 'do-not-disturb', + 'mood' : 'A zip line but from the sofa to the fridge', + }, + { + 'id' : '5725a6808a178bfd034d6ecf', + 'name' : 'Mai', + 'avatar': 'assets/images/avatars/Mai.jpg', + 'status': 'away', + 'mood' : 'If a girl tells you she has a nipple ring, the only correct response is \'I don\'t believe you.\'', + }, + { + 'id' : '5725a680653c265f5c79b5a9', + 'name' : 'Nancy', + 'avatar': 'assets/images/avatars/Nancy.jpg', + 'status': 'do-not-disturb', + 'mood' : 'Prison counts as a gated community, right?', + }, + { + 'id' : '5725a680bbcec3cc32a8488a', + 'name' : 'Nora', + 'avatar': 'assets/images/avatars/Nora.jpg', + 'status': 'do-not-disturb', + 'mood' : 'I never date left handed women. Righty tighty, lefty loosey.', + }, + { + 'id' : '5725a6803d87f1b77e17b62b', + 'name' : 'Odessa', + 'avatar': 'assets/images/avatars/Odessa.jpg', + 'status': 'away', + 'mood' : 'A day without sunshine is like, night.', + }, + { + 'id' : '5725a680e87cb319bd9bd673', + 'name' : 'Reyna', + 'avatar': 'assets/images/avatars/Reyna.jpg', + 'status': 'offline', + 'mood' : 'I can\'t wait for summer in Canada...', + }, + { + 'id' : '5725a6802d10e277a0f35775', + 'name' : 'Shauna', + 'avatar': 'assets/images/avatars/Shauna.jpg', + 'status': 'online', + 'mood' : 'My take home pay doesn’t ven take me home.', + 'unread': null, + }, + { + 'id' : '5725a680aef1e5cf26dd3d1f', + 'name' : 'Shepard', + 'avatar': 'assets/images/avatars/Shepard.jpg', + 'status': 'online', + 'mood' : 'I don\'t speak Spanish, but I\'m pretty sure \'Dora\' means \'annoying\'', + }, + { + 'id' : '5725a680cd7efa56a45aea5d', + 'name' : 'Tillman', + 'avatar': 'assets/images/avatars/Tillman.jpg', + 'status': 'do-not-disturb', + 'mood' : '', + }, + { + 'id' : '5725a680fb65c91a82cb35e2', + 'name' : 'Trevino', + 'avatar': 'assets/images/avatars/Trevino.jpg', + 'status': 'away', + 'mood' : 'Apparently, a rat and a plastic tube does not count as a DIY abortion kit.', + }, + { + 'id' : '5725a68018c663044be49cbf', + 'name' : 'Tyson', + 'avatar': 'assets/images/avatars/Tyson.jpg', + 'status': 'do-not-disturb', + 'mood' : 'I\'m wondering why life keeps teaching me lessons I have no desire to learn...', + }, + { + 'id' : '5725a6809413bf8a0a5272b1', + 'name' : 'Velazquez', + 'avatar': 'assets/images/avatars/Velazquez.jpg', + 'status': 'online', + 'mood' : 'Modulation in all things.', + } + ]; + + 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' : 'it\'s a status....not your diary...', + 'chatList': [ + { + 'id' : '1725a680b3249760ea21de52', + 'contactId' : '5725a680b3249760ea21de52', + 'name' : 'Alice Freeman', + 'unread' : 4, + 'lastMessageTime': '2017-06-12T02:10:18.931Z' + }, + { + 'id' : '2725a680b8d240c011dd2243', + 'contactId' : '5725a680b8d240c011dd224b', + 'name' : 'Josefina', + 'unread' : null, + 'lastMessageTime': '2017-02-18T10:30:18.931Z' + }, + { + 'id' : '3725a6809413bf8a0a5272b4', + 'contactId' : '5725a6809413bf8a0a5272b1', + 'name' : 'Velazquez', + 'unread' : 2, + 'lastMessageTime': '2017-03-18T12:30:18.931Z' + } + ] + } + ]; + +} diff --git a/src/app/fuse-fake-db/fuse-fake-db.service.ts b/src/app/fuse-fake-db/fuse-fake-db.service.ts index 143b82d6..dc3b15bf 100644 --- a/src/app/fuse-fake-db/fuse-fake-db.service.ts +++ b/src/app/fuse-fake-db/fuse-fake-db.service.ts @@ -1,5 +1,6 @@ import {InMemoryDbService} from 'angular-in-memory-web-api'; import {MailFakeDb} from './mail'; +import {ChatFakeDb} from './chat'; export class FuseFakeDbService implements InMemoryDbService { @@ -7,8 +8,11 @@ export class FuseFakeDbService implements InMemoryDbService { return { 'mail-mails' : MailFakeDb.mails, - 'mail-folders': MailFakeDb.folders, - 'mail-labels' : MailFakeDb.labels + 'mail-folders' : MailFakeDb.folders, + 'mail-labels' : MailFakeDb.labels, + 'chat-contacts': ChatFakeDb.contacts, + 'chat-chats' : ChatFakeDb.chats, + 'chat-user' : ChatFakeDb.user, }; } diff --git a/src/app/main/apps/chat/chat-start/chat-start.component.html b/src/app/main/apps/chat/chat-start/chat-start.component.html new file mode 100644 index 00000000..d07bb51d --- /dev/null +++ b/src/app/main/apps/chat/chat-start/chat-start.component.html @@ -0,0 +1,17 @@ +
+ +
+ + chat + +
+ + Chat App + + Select contact to start the chat!.. + + + +
diff --git a/src/app/main/apps/chat/chat-start/chat-start.component.scss b/src/app/main/apps/chat/chat-start/chat-start.component.scss new file mode 100644 index 00000000..b0e1afde --- /dev/null +++ b/src/app/main/apps/chat/chat-start/chat-start.component.scss @@ -0,0 +1,30 @@ +@import "src/app/core/scss/fuse"; + +:host { + display: flex; + flex: 1; + height: 100%; + background: linear-gradient(to bottom, rgba(255, 255, 255, 0.8) 0%, rgba(255, 255, 255, 0.6) 20%, rgba(255, 255, 255, 0.8)); + + .big-circle { + background: linear-gradient(to bottom, rgba(255, 255, 255, 0.8) 0%, rgba(255, 255, 255, 0.6) 20%, rgba(255, 255, 255, 0.8)); + border-radius: 50%; + width: 300px; + height: 300px; + line-height: 300px; + text-align: center; + + md-icon{ + color: mat-color($accent); + } + } + + .app-title { + font-weight: 500; + font-size: 32px; + } + + .secondary-text { + font-size: 16px; + } +} diff --git a/src/app/main/apps/chat/chat-start/chat-start.component.ts b/src/app/main/apps/chat/chat-start/chat-start.component.ts new file mode 100644 index 00000000..ab7a6e51 --- /dev/null +++ b/src/app/main/apps/chat/chat-start/chat-start.component.ts @@ -0,0 +1,19 @@ +import {Component, OnInit} from '@angular/core'; + +@Component({ + selector : 'fuse-chat-start', + templateUrl: './chat-start.component.html', + styleUrls : ['./chat-start.component.scss'] +}) +export class ChatStartComponent implements OnInit +{ + + constructor() + { + } + + ngOnInit() + { + } + +} diff --git a/src/app/main/apps/chat/chat-view/chat-view.component.html b/src/app/main/apps/chat/chat-view/chat-view.component.html new file mode 100644 index 00000000..7168346f --- /dev/null +++ b/src/app/main/apps/chat/chat-view/chat-view.component.html @@ -0,0 +1,115 @@ + +
+ + + + +
+ +
+ + +
+ chat +
+ + + +
+ +
+ + {{contact.name}} + + + +
+ +
+ {{contact.name}} +
+ +
+ +
+ +
+ + + + + +
+ +
+
+ + + +
+ + +
+ + +
+ + {{contact.name}} + + + +
+
{{message.message}}
+
{{message.time | date:'medium'}}
+
+ +
+ + +
+ + +
+ + + + + +
+ diff --git a/src/app/main/apps/chat/chat-view/chat-view.component.scss b/src/app/main/apps/chat/chat-view/chat-view.component.scss new file mode 100644 index 00000000..90a8a613 --- /dev/null +++ b/src/app/main/apps/chat/chat-view/chat-view.component.scss @@ -0,0 +1,135 @@ +:host { + display: flex; + flex: 1; + height: 100%; + background: linear-gradient(to bottom, rgba(255, 255, 255, 0.8) 0%, rgba(255, 255, 255, 0.6) 20%, rgba(255, 255, 255, 0.8)); + overflow: hidden; + + .chat { + height: 100%; + + .chat-toolbar { + min-height: 64px; + background-color: #F3F4F5; + color: rgba(0, 0, 0, 0.87); + border-bottom: 1px solid rgba(0, 0, 0, .08); + + .responsive-chats-button { + padding: 0; + } + + .chat-contact { + cursor: pointer; + + .avatar { + margin-right: 16px; + } + + .chat-contact-name { + + } + } + } + + #chat-content { + background: transparent; + + .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=); + } + } + } + } + } + + .chat-footer { + min-height: 64px; + max-height: 96px; + background-color: #F3F4F5; + color: rgba(0, 0, 0, 0.87); + border-top: 1px solid rgba(0, 0, 0, .08); + padding: 8px 8px 8px 16px; + + .reply-form { + + md-input-container { + margin: 0; + padding-right: 16px; + + textarea { + overflow: auto; + max-height: 80px; + transition: height 200ms ease; + &.grow { + height: 80px; + } + } + + .md-errors-spacer { + display: none; + } + } + + .md-button { + margin: 0; + } + } + } + } +} diff --git a/src/app/main/apps/chat/chat-view/chat-view.component.ts b/src/app/main/apps/chat/chat-view/chat-view.component.ts new file mode 100644 index 00000000..24bcf63d --- /dev/null +++ b/src/app/main/apps/chat/chat-view/chat-view.component.ts @@ -0,0 +1,101 @@ +import { AfterViewInit, Component, OnInit, ViewChild, ViewChildren } from '@angular/core'; +import { ChatService } from '../chat.service'; +import { NgForm } from '@angular/forms'; +import { PerfectScrollbarDirective } from 'ngx-perfect-scrollbar'; + +@Component({ + selector : 'fuse-chat-view', + templateUrl: './chat-view.component.html', + styleUrls : ['./chat-view.component.scss'] +}) +export class ChatViewComponent implements OnInit, AfterViewInit +{ + user: any; + chat: any; + dialog: any; + contact: any; + replyInput: any; + selectedChat: any; + @ViewChild(PerfectScrollbarDirective) directiveScroll: PerfectScrollbarDirective; + @ViewChildren('replyInput') replyInputField; + @ViewChild('replyForm') replyForm: NgForm; + + constructor(private chatService: ChatService) + { + } + + ngOnInit() + { + this.user = this.chatService.user; + this.chatService.onChatSelected + .subscribe(chatData => { + if ( chatData ) + { + this.selectedChat = chatData; + this.contact = chatData.contact; + this.dialog = chatData.dialog; + this.readyToReply(); + } + }); + } + + ngAfterViewInit() + { + this.replyInput = this.replyInputField.first.nativeElement; + this.readyToReply(); + } + + selectContact() + { + this.chatService.selectContact(this.contact); + } + + readyToReply() + { + setTimeout(() => { + this.replyForm.reset(); + this.focusReplyInput(); + this.scrollToBottom(); + }); + + } + + focusReplyInput() + { + setTimeout(() => { + this.replyInput.focus(); + }); + } + + scrollToBottom(speed?: number) + { + speed = speed || 400; + if ( this.directiveScroll ) + { + setTimeout(() => { + this.directiveScroll.scrollToBottom(0, speed); + }); + } + + } + + + reply(event) + { + // Message + const message = { + who : this.user.id, + message: this.replyForm.form.value.message, + time : new Date().toISOString() + }; + + // Add the message to the chat + this.dialog.push(message); + + // Update the server + this.chatService.updateDialog(this.selectedChat.chatId, this.dialog).then(response => { + this.readyToReply(); + }); + + } +} diff --git a/src/app/main/apps/chat/chat.component.html b/src/app/main/apps/chat/chat.component.html index 3a6e1396..145788aa 100644 --- a/src/app/main/apps/chat/chat.component.html +++ b/src/app/main/apps/chat/chat.component.html @@ -1,3 +1,43 @@ -

- chat works! -

+
+ + +
+ + + +
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ + +
diff --git a/src/app/main/apps/chat/chat.component.scss b/src/app/main/apps/chat/chat.component.scss index e69de29b..9f6a3c33 100644 --- a/src/app/main/apps/chat/chat.component.scss +++ b/src/app/main/apps/chat/chat.component.scss @@ -0,0 +1,31 @@ +:host { + height: 100% !important; + + .center { + padding: 32px 32px 0 32px; + max-width: 1400px; + height: 100%; + margin: 0 auto; + + .content-card { + position: relative; + background: url('/assets/images/patterns/rain-grey.png') repeat; + height: 100%; + + .mat-sidenav-container { + width: 100%; + height: 100%; + background: transparent; + + md-sidenav { + display: flex; + flex-direction: column; + width: 400px; + max-width: 90%; + box-shadow: 0 0 1px rgba(0, 0, 0, .37); + overflow: hidden; + } + } + } + } +} diff --git a/src/app/main/apps/chat/chat.component.ts b/src/app/main/apps/chat/chat.component.ts index dae90173..85c78ee9 100644 --- a/src/app/main/apps/chat/chat.component.ts +++ b/src/app/main/apps/chat/chat.component.ts @@ -1,15 +1,25 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, OnInit, ViewEncapsulation } from '@angular/core'; +import { ChatService } from './chat.service'; @Component({ - selector: 'fuse-chat', - templateUrl: './chat.component.html', - styleUrls: ['./chat.component.scss'] + selector : 'fuse-chat', + templateUrl: './chat.component.html', + styleUrls : ['./chat.component.scss'] }) -export class ChatComponent implements OnInit { +export class ChatComponent implements OnInit +{ + selectedChat: any; - constructor() { } + constructor(private chatService: ChatService) + { + } - ngOnInit() { - } + ngOnInit() + { + this.chatService.onChatSelected + .subscribe(chatData => { + this.selectedChat = chatData; + }); + } } diff --git a/src/app/main/apps/chat/chat.module.ts b/src/app/main/apps/chat/chat.module.ts index 4f5050d8..284b9406 100644 --- a/src/app/main/apps/chat/chat.module.ts +++ b/src/app/main/apps/chat/chat.module.ts @@ -2,10 +2,21 @@ import {NgModule} from '@angular/core'; import {SharedModule} from '../../../core/modules/shared.module'; import {RouterModule, Routes} from '@angular/router'; import {ChatComponent} from './chat.component'; +import {ChatService} from './chat.service'; +import { ChatViewComponent } from './chat-view/chat-view.component'; +import { ChatStartComponent } from './chat-start/chat-start.component'; +import {ChatsSidenavComponent} from './sidenavs/left/chats/chats.component'; +import { UserSidenavComponent } from './sidenavs/left/user/user.component'; +import { LeftSidenavComponent } from './sidenavs/left/left.component'; +import { RightSidenavComponent } from './sidenavs/right/right.component'; +import { ContactSidenavComponent } from './sidenavs/right/contact/contact.component'; const routes: Routes = [ { - path: 'apps/chat', component: ChatComponent, children: [] + path : 'apps/chat', component: ChatComponent, children: [], + resolve: { + chat: ChatService + } } ]; @@ -15,7 +26,17 @@ const routes: Routes = [ RouterModule.forChild(routes) ], declarations: [ - ChatComponent + ChatComponent, + ChatViewComponent, + ChatStartComponent, + ChatsSidenavComponent, + UserSidenavComponent, + LeftSidenavComponent, + RightSidenavComponent, + ContactSidenavComponent + ], + providers : [ + ChatService ] }) export class ChatModule diff --git a/src/app/main/apps/chat/chat.service.ts b/src/app/main/apps/chat/chat.service.ts new file mode 100644 index 00000000..484603d8 --- /dev/null +++ b/src/app/main/apps/chat/chat.service.ts @@ -0,0 +1,259 @@ +import { Injectable } from '@angular/core'; +import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router'; +import { Observable } from 'rxjs/Observable'; +import { Http } from '@angular/http'; +import { Subject } from 'rxjs/Subject'; +import { BehaviorSubject } from 'rxjs/BehaviorSubject'; + +@Injectable() +export class ChatService implements Resolve +{ + contacts: any[]; + chats: any[]; + user: any; + onChatSelected = new BehaviorSubject(null); + onContactSelected = new BehaviorSubject(null); + onChatsUpdated = new Subject(); + onUserUpdated = new Subject(); + onLeftSidenavViewChanged = new Subject(); + onRightSidenavViewChanged = new Subject(); + + constructor(private http: Http) + { + } + + /** + * Get chat + * @param contactId + * @returns {Promise} + */ + getChat(contactId) + { + const chatItem = this.user.chatList.find((item) => { + return item.contactId === contactId; + }); + + /** + * Create new chat, if it's not created yet. + */ + if ( !chatItem ) + { + this.createNewChat(contactId).then((newChats) => { + this.getChat(contactId); + }); + return; + } + + return new Promise((resolve, reject) => { + this.http.get('api/chat-chats/' + chatItem.id) + .subscribe(response => { + const chat = response.json().data; + + const chatContact = this.contacts.find((contact) => { + return contact.id === contactId; + }); + + const chatData = { + chatId : chat.id, + dialog : chat.dialog, + contact: chatContact + }; + + this.onChatSelected.next({...chatData}); + + }, reject); + + }); + + } + + /** + * Create New Chat + * @param contactId + * @returns {Promise} + */ + createNewChat(contactId) + { + return new Promise((resolve, reject) => { + + const contact = this.contacts.find((item) => { + return item.id === contactId; + }); + + const chatId = this.guidGenerator(); + + const chat = { + id : chatId, + dialog: [] + }; + + const chatListItem = { + 'id' : chatId, + 'contactId' : contactId, + 'unread' : null, + '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 + */ + this.http.post('api/chat-chats', {...chat}) + .subscribe(response => { + + /** + * Post the new the user data + */ + this.http.post('api/chat-user/' + this.user.id, this.user) + .subscribe(addedlistItem => { + + /** + * Update the user data from server + */ + this.getUser().then(updatedUser => { + this.onUserUpdated.next(updatedUser); + resolve(updatedUser); + }); + }); + }, reject); + }); + } + + /** + * Select Contact + * @param contact + */ + selectContact(contact) + { + this.onContactSelected.next(contact); + } + + /** + * Set user status + * @param status + */ + setUserStatus(status) + { + this.user.status = status; + } + + /** + * Update user data + * @param userData + */ + updateUserData(userData) + { + this.http.post('api/chat-user/' + this.user.id, userData) + .subscribe(response => { + this.user = userData; + } + ); + } + + /** + * Update the chat dialog + * @param chatId + * @param dialog + * @returns {Promise} + */ + updateDialog(chatId, dialog): Promise + { + return new Promise((resolve, reject) => { + + const newData = { + id : chatId, + dialog: dialog + }; + + this.http.post('api/chat-chats/' + chatId, newData) + .subscribe(updatedChat => { + resolve(updatedChat); + }, reject); + }); + } + + /** + * The Mail App Main Resolver + * @param {ActivatedRouteSnapshot} route + * @param {RouterStateSnapshot} state + * @returns {Observable | Promise | any} + */ + resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable | Promise | any + { + return new Promise((resolve, reject) => { + Promise.all([ + this.getContacts(), + this.getChats(), + this.getUser() + ]).then( + ([contacts, chats, user]) => { + this.contacts = contacts; + this.chats = chats; + this.user = user; + resolve(); + }, + reject + ); + }); + } + + /** + * Get Contacts + * @returns {Promise} + */ + getContacts(): Promise + { + return new Promise((resolve, reject) => { + this.http.get('api/chat-contacts') + .subscribe(response => { + resolve(response.json().data); + }, reject); + }); + } + + /** + * Get Chats + * @returns {Promise} + */ + getChats(): Promise + { + return new Promise((resolve, reject) => { + this.http.get('api/chat-chats') + .subscribe(response => { + resolve(response.json().data); + }, reject); + }); + } + + /** + * Get User + * @returns {Promise} + */ + getUser(): Promise + { + return new Promise((resolve, reject) => { + this.http.get('api/chat-user') + .subscribe(response => { + resolve(response.json().data[0]); + }, reject); + }); + } + + /** + * Random ID Generator + * @returns {string} + */ + guidGenerator() + { + function S4() + { + return (((1 + Math.random()) * 0x10000) || 0).toString(16).substring(1); + } + + return (S4() + S4()); + } +} diff --git a/src/app/main/apps/chat/sidenavs/left/chats/chats.component.html b/src/app/main/apps/chat/sidenavs/left/chats/chats.component.html new file mode 100644 index 00000000..04cb1cc8 --- /dev/null +++ b/src/app/main/apps/chat/sidenavs/left/chats/chats.component.html @@ -0,0 +1,183 @@ + +
+ + + + +
+ + +
+ + + {{user.name}} + + + + + + + + + + + + + + + + + +
+ + +
+ + + + + +
+
+ + + + + + +
+ + +
+ + +
+ + +
+ + +
+ + + +
+ + +
+ + +
+ +
+ Chats +
+ + + +
+ + + +
+ +
+ Contacts +
+ + +
+ + + +
+ No results.. +
+ + +
+ + +
+ diff --git a/src/app/main/apps/chat/sidenavs/left/chats/chats.component.scss b/src/app/main/apps/chat/sidenavs/left/chats/chats.component.scss new file mode 100644 index 00000000..c2b36587 --- /dev/null +++ b/src/app/main/apps/chat/sidenavs/left/chats/chats.component.scss @@ -0,0 +1,110 @@ +@import "src/app/core/scss/fuse"; + +:host { + display: flex; + flex: 1; + flex-direction: column; + + .sidenav-header { + + md-toolbar { + border-bottom: 1px solid rgba(0, 0, 0, .08); + + .avatar-wrapper { + + .avatar, .status { + cursor: pointer; + } + } + + .search { + height: 36px; + line-height: 36px; + padding: 8px; + background: #FFFFFF; + font-size: 13px; + @include mat-elevation(1); + + .icon { + margin: 0; + color: rgba(0, 0, 0, 0.54); + } + + input { + padding-left: 12px; + height: 36px; + color: rgba(0, 0, 0, 0.54); + border: none; + } + } + } + } + + .sidenav-content { + .contact-list, .chat-list { + + .mat-subheader { + padding-left: 16px; + font-size: 20px; + font-weight: 300; + height: 88px; + line-height: 88px; + color: mat-color($accent); + } + + .contact { + white-space: normal; + text-align: left; + letter-spacing: .010em; + min-height: 88px; + border-bottom: 1px solid rgba(0, 0, 0, 0.12); + padding: 16px; + font-weight: 400; + + .avatar-wrapper { + + .avatar { + margin-right: 16px; + } + } + + .contact-name { + font-size: 16px; + white-space: nowrap; + text-overflow: ellipsis; + } + + .contact-last-message { + line-height: 1.6em; + margin: 0; + font-weight: 500; + color: rgba(0, 0, 0, 0.54); + } + + .contact-mood { + + } + + .unread-message-count { + border-radius: 50%; + text-align: center; + width: 24px; + height: 24px; + line-height: 24px; + background-color: mat-color($accent); + color: map-get($accent, default-contrast); + } + } + } + + .no-results-message { + position: absolute; + width: 100%; + height: 88px; + padding: 16px; + background: #FFFFFF; + font-size: 15px; + font-weight: 400; + } + } +} diff --git a/src/app/main/apps/chat/sidenavs/left/chats/chats.component.ts b/src/app/main/apps/chat/sidenavs/left/chats/chats.component.ts new file mode 100644 index 00000000..76ddc7c1 --- /dev/null +++ b/src/app/main/apps/chat/sidenavs/left/chats/chats.component.ts @@ -0,0 +1,66 @@ +import { Component, OnInit } from '@angular/core'; +import { ChatService } from '../../../chat.service'; +import { FuseMdSidenavHelperService } from '../../../../../../core/directives/md-sidenav-helper/md-sidenav-helper.service'; +import { ObservableMedia } from '@angular/flex-layout'; + +@Component({ + selector : 'fuse-chat-chats-sidenav', + templateUrl: './chats.component.html', + styleUrls : ['./chats.component.scss'] +}) +export class ChatsSidenavComponent implements OnInit +{ + user: any; + chats: any[]; + contacts: any[]; + chatSearch: any; + + constructor(private chatService: ChatService, + private fuseMdSidenavService: FuseMdSidenavHelperService, + public media: ObservableMedia) + { + this.chatSearch = { + name: '' + }; + } + + ngOnInit() + { + this.user = this.chatService.user; + this.chats = this.chatService.chats; + this.contacts = this.chatService.contacts; + + this.chatService.onChatsUpdated.subscribe(updatedChats => { + this.chats = updatedChats; + }); + + this.chatService.onUserUpdated.subscribe(updatedUser => { + this.user = updatedUser; + }); + } + + getChat(contact) + { + this.chatService.getChat(contact); + + if ( !this.media.isActive('gt-md') ) + { + this.fuseMdSidenavService.getSidenav('chat-left-sidenav').toggle(); + } + } + + setUserStatus(status) + { + this.chatService.setUserStatus(status); + } + + changeLeftSidenavView(view) + { + this.chatService.onLeftSidenavViewChanged.next(view); + } + + logout() + { + console.info('logout triggered'); + } +} diff --git a/src/app/main/apps/chat/sidenavs/left/left.component.html b/src/app/main/apps/chat/sidenavs/left/left.component.html new file mode 100644 index 00000000..43df8660 --- /dev/null +++ b/src/app/main/apps/chat/sidenavs/left/left.component.html @@ -0,0 +1,11 @@ +
+ + + + + +
diff --git a/src/app/main/apps/chat/sidenavs/left/left.component.scss b/src/app/main/apps/chat/sidenavs/left/left.component.scss new file mode 100644 index 00000000..3661f0ce --- /dev/null +++ b/src/app/main/apps/chat/sidenavs/left/left.component.scss @@ -0,0 +1,20 @@ +:host { + display: flex; + flex-direction: column; + height: 100%; + + .views { + display: flex; + flex-direction: column; + height: 100%; + + .view { + position: absolute; + height: 100%; + bottom: 0; + left: 0; + right: 0; + top: 0; + } + } +} diff --git a/src/app/main/apps/chat/sidenavs/left/left.component.ts b/src/app/main/apps/chat/sidenavs/left/left.component.ts new file mode 100644 index 00000000..0b16799f --- /dev/null +++ b/src/app/main/apps/chat/sidenavs/left/left.component.ts @@ -0,0 +1,27 @@ +import { Component, OnInit } from '@angular/core'; +import { Animations } from '../../../../../core/animations'; +import { ChatService } from '../../chat.service'; + +@Component({ + selector : 'fuse-chat-left-sidenav', + templateUrl: './left.component.html', + styleUrls : ['./left.component.scss'], + animations : [Animations.slideInLeft, Animations.slideInRight] +}) +export class LeftSidenavComponent implements OnInit +{ + view: string; + + constructor(private chatService: ChatService) + { + this.view = 'chats'; + } + + ngOnInit() + { + this.chatService.onLeftSidenavViewChanged.subscribe(view => { + this.view = view; + }); + } + +} diff --git a/src/app/main/apps/chat/sidenavs/left/user/user.component.html b/src/app/main/apps/chat/sidenavs/left/user/user.component.html new file mode 100644 index 00000000..00fa8bbb --- /dev/null +++ b/src/app/main/apps/chat/sidenavs/left/user/user.component.html @@ -0,0 +1,78 @@ + +
+ + + + + +
+ + + +
+ + + + + + {{user.name}} + +
{{user.name}}
+ +
+ + +
+ +
+ + +
+ + + + +
+ + + + + + + + +
+ + Online +
+
+ + +
+ + Away +
+
+ + +
+ + Do not disturb +
+
+ + +
+ + Offline +
+
+
+ +
+
+ +
diff --git a/src/app/main/apps/chat/sidenavs/left/user/user.component.scss b/src/app/main/apps/chat/sidenavs/left/user/user.component.scss new file mode 100644 index 00000000..9a844a8f --- /dev/null +++ b/src/app/main/apps/chat/sidenavs/left/user/user.component.scss @@ -0,0 +1,21 @@ +@import "src/app/core/scss/fuse"; + +:host { + display: flex; + flex: 1; + flex-direction: column; + + md-toolbar { + background-color: mat-color($accent); + color: map-get($accent, default-contrast); + + .toolbar-bottom { + height: 240px; + } + + } + + .sidenav-content{ + background: whitesmoke; + } +} diff --git a/src/app/main/apps/chat/sidenavs/left/user/user.component.ts b/src/app/main/apps/chat/sidenavs/left/user/user.component.ts new file mode 100644 index 00000000..9f4ce7bc --- /dev/null +++ b/src/app/main/apps/chat/sidenavs/left/user/user.component.ts @@ -0,0 +1,47 @@ +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { ChatService } from '../../../chat.service'; +import { FormControl, FormGroup } from '@angular/forms'; +import 'rxjs/Rx'; + +@Component({ + selector : 'fuse-chat-user-sidenav', + templateUrl: './user.component.html', + styleUrls : ['./user.component.scss'] +}) +export class UserSidenavComponent implements OnInit, OnDestroy +{ + user: any; + onFormChange: any; + userForm: FormGroup; + + constructor(private chatService: ChatService) + { + this.user = this.chatService.user; + this.userForm = new FormGroup({ + mood : new FormControl(this.user.mood), + status: new FormControl(this.user.status) + }); + } + + ngOnInit() + { + this.onFormChange = this.userForm.valueChanges + .debounceTime(500) + .distinctUntilChanged() + .subscribe(data => { + this.user.mood = data.mood; + this.user.status = data.status; + this.chatService.updateUserData(this.user); + }); + } + + changeLeftSidenavView(view) + { + this.chatService.onLeftSidenavViewChanged.next(view); + } + + ngOnDestroy() + { + this.onFormChange.unsubscribe(); + } +} diff --git a/src/app/main/apps/chat/sidenavs/right/contact/contact.component.html b/src/app/main/apps/chat/sidenavs/right/contact/contact.component.html new file mode 100644 index 00000000..2677e347 --- /dev/null +++ b/src/app/main/apps/chat/sidenavs/right/contact/contact.component.html @@ -0,0 +1,47 @@ + +
+ + + + + +
+ +
Contact Info
+ + + +
+ + + + + + {{contact.name}} + +
{{contact.name}}
+ +
+ + +
+ +
+ + +
+ + + + + + + + + + +
diff --git a/src/app/main/apps/chat/sidenavs/right/contact/contact.component.scss b/src/app/main/apps/chat/sidenavs/right/contact/contact.component.scss new file mode 100644 index 00000000..9a844a8f --- /dev/null +++ b/src/app/main/apps/chat/sidenavs/right/contact/contact.component.scss @@ -0,0 +1,21 @@ +@import "src/app/core/scss/fuse"; + +:host { + display: flex; + flex: 1; + flex-direction: column; + + md-toolbar { + background-color: mat-color($accent); + color: map-get($accent, default-contrast); + + .toolbar-bottom { + height: 240px; + } + + } + + .sidenav-content{ + background: whitesmoke; + } +} diff --git a/src/app/main/apps/chat/sidenavs/right/contact/contact.component.ts b/src/app/main/apps/chat/sidenavs/right/contact/contact.component.ts new file mode 100644 index 00000000..70a71789 --- /dev/null +++ b/src/app/main/apps/chat/sidenavs/right/contact/contact.component.ts @@ -0,0 +1,25 @@ +import { Component, OnInit } from '@angular/core'; +import { ChatService } from '../../../chat.service'; + +@Component({ + selector : 'fuse-chat-contact-sidenav', + templateUrl: './contact.component.html', + styleUrls : ['./contact.component.scss'] +}) +export class ContactSidenavComponent implements OnInit +{ + contact: any; + + constructor(private chatService: ChatService) + { + + } + + ngOnInit() + { + this.chatService.onContactSelected.subscribe(contact => { + this.contact = contact; + }); + } + +} diff --git a/src/app/main/apps/chat/sidenavs/right/right.component.html b/src/app/main/apps/chat/sidenavs/right/right.component.html new file mode 100644 index 00000000..a193e1f3 --- /dev/null +++ b/src/app/main/apps/chat/sidenavs/right/right.component.html @@ -0,0 +1,8 @@ +
+ + + + +
diff --git a/src/app/main/apps/chat/sidenavs/right/right.component.scss b/src/app/main/apps/chat/sidenavs/right/right.component.scss new file mode 100644 index 00000000..3661f0ce --- /dev/null +++ b/src/app/main/apps/chat/sidenavs/right/right.component.scss @@ -0,0 +1,20 @@ +:host { + display: flex; + flex-direction: column; + height: 100%; + + .views { + display: flex; + flex-direction: column; + height: 100%; + + .view { + position: absolute; + height: 100%; + bottom: 0; + left: 0; + right: 0; + top: 0; + } + } +} diff --git a/src/app/main/apps/chat/sidenavs/right/right.component.ts b/src/app/main/apps/chat/sidenavs/right/right.component.ts new file mode 100644 index 00000000..3006f034 --- /dev/null +++ b/src/app/main/apps/chat/sidenavs/right/right.component.ts @@ -0,0 +1,27 @@ +import { Component, OnInit } from '@angular/core'; +import { Animations } from '../../../../../core/animations'; +import { ChatService } from '../../chat.service'; + +@Component({ + selector : 'fuse-chat-right-sidenav', + templateUrl: './right.component.html', + styleUrls : ['./right.component.scss'], + animations : [Animations.slideInLeft, Animations.slideInRight] +}) +export class RightSidenavComponent implements OnInit +{ + view: string; + + constructor(private chatService: ChatService) + { + this.view = 'contact'; + } + + ngOnInit() + { + this.chatService.onRightSidenavViewChanged.subscribe(view => { + this.view = view; + }); + } + +} diff --git a/tslint.json b/tslint.json index 62ab1fea..576a0287 100644 --- a/tslint.json +++ b/tslint.json @@ -26,7 +26,7 @@ "label-position": true, "max-line-length": [ true, - 120 + 180 ], "member-access": false, "member-ordering": [