diff --git a/package-lock.json b/package-lock.json
index d91a2334..d9432cb3 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -367,11 +367,33 @@
"integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=",
"dev": true
},
+ "angular-calendar": {
+ "version": "0.19.0",
+ "resolved": "https://registry.npmjs.org/angular-calendar/-/angular-calendar-0.19.0.tgz",
+ "integrity": "sha512-RbLciS+OBl9ITgPKVUOjMUXe7sTP5KHPZtCB7Rru3ebUXh8WehdZvELC3Wxz0euS8hqBP2o3ueSqXcAy1pz2bw==",
+ "requires": {
+ "angular-draggable-droppable": "1.0.1",
+ "angular-resizable-element": "1.2.0",
+ "calendar-utils": "0.0.56",
+ "date-fns": "1.28.5",
+ "positioning": "1.3.0"
+ }
+ },
+ "angular-draggable-droppable": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/angular-draggable-droppable/-/angular-draggable-droppable-1.0.1.tgz",
+ "integrity": "sha1-etcMMQmUsPmA9A04Lc5ZlG/jDc8="
+ },
"angular-in-memory-web-api": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/angular-in-memory-web-api/-/angular-in-memory-web-api-0.3.2.tgz",
"integrity": "sha1-iDbZ4lNNN7co88taHK9v4ef7vs0="
},
+ "angular-resizable-element": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/angular-resizable-element/-/angular-resizable-element-1.2.0.tgz",
+ "integrity": "sha512-i5xCl4n2VMgGK4gY6Jtho0K5aazbsqNw1bmPYpI9RwlKK+dIOcsMRuMl1JPWzrznHsm4qEsfYg+9KLkYsYy+/g=="
+ },
"ansi-escapes": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-2.0.0.tgz",
@@ -1043,6 +1065,11 @@
"integrity": "sha1-TJQj6i0lLCcMQbK97+/5u2tiwGo=",
"dev": true
},
+ "calendar-utils": {
+ "version": "0.0.56",
+ "resolved": "https://registry.npmjs.org/calendar-utils/-/calendar-utils-0.0.56.tgz",
+ "integrity": "sha512-IvvzvIGmtDdVjSrnIOd5dDJ3ATWLztqdgLOTeIB2IJonN60LMa2R71KDVpdF8w9sKm3drRV4kfgIbocYJAw6+Q=="
+ },
"callsite": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz",
@@ -1837,6 +1864,11 @@
}
}
},
+ "date-fns": {
+ "version": "1.28.5",
+ "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.28.5.tgz",
+ "integrity": "sha1-JXz8RdMi30XvVlhmWWfuhBzXP68="
+ },
"date-now": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz",
@@ -5684,6 +5716,11 @@
"integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=",
"dev": true
},
+ "ngx-color-picker": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/ngx-color-picker/-/ngx-color-picker-4.2.0.tgz",
+ "integrity": "sha512-xWFpvOc+0WOD2kppPDlN1q5p58jgQDgUSsier/xi1i0HaVuU+BgNCo7aFPAKHaovw0Gv1WWp5GPAdpjXdUe7KA=="
+ },
"ngx-perfect-scrollbar": {
"version": "4.5.2",
"resolved": "https://registry.npmjs.org/ngx-perfect-scrollbar/-/ngx-perfect-scrollbar-4.5.2.tgz",
@@ -6275,6 +6312,11 @@
}
}
},
+ "positioning": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/positioning/-/positioning-1.3.0.tgz",
+ "integrity": "sha512-B0BHlhLFsLPV8EWVv792caQCg4QNxuCeZUVXw/DP1jRj4WOF74KmTAg+7t3dDfrFXDT22qBS2vcryQmZYSM7jg=="
+ },
"postcss": {
"version": "5.2.17",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.17.tgz",
diff --git a/package.json b/package.json
index 265eec89..168d9d0e 100644
--- a/package.json
+++ b/package.json
@@ -18,28 +18,30 @@
"@angular/compiler": "^4.3.1",
"@angular/core": "^4.3.1",
"@angular/flex-layout": "2.0.0-beta.8",
- "@angular/forms": "^4.3.1",
- "@angular/http": "^4.3.1",
+ "@angular/forms": "^4.3.0",
+ "@angular/http": "^4.3.0",
"@angular/material": "^2.0.0-beta.8",
"@angular/platform-browser": "^4.3.1",
"@angular/platform-browser-dynamic": "^4.3.1",
"@angular/router": "^4.3.1",
"@swimlane/ngx-datatable": "^9.3.1",
+ "angular-calendar": "^0.19.0",
"angular-in-memory-web-api": "^0.3.2",
"core-js": "^2.4.1",
- "firebase": "^4.1.5",
+ "firebase": "^4.1.3",
"hammerjs": "^2.0.8",
+ "ngx-color-picker": "^4.2.0",
"ngx-perfect-scrollbar": "^4.5.2",
"rxjs": "^5.4.2",
- "zone.js": "^0.8.14"
+ "zone.js": "^0.8.13"
},
"devDependencies": {
- "@angular/cli": "^1.2.4",
- "@angular/compiler-cli": "^4.3.1",
- "@angular/language-service": "^4.3.1",
+ "@angular/cli": "^1.2.1",
+ "@angular/compiler-cli": "^4.3.0",
+ "@angular/language-service": "^4.3.0",
"@types/jasmine": "^2.5.53",
"@types/jasminewd2": "^2.0.2",
- "@types/node": "^6.0.85",
+ "@types/node": "^6.0.83",
"codelyzer": "~3.0.1",
"jasmine-core": "~2.6.2",
"jasmine-spec-reporter": "~4.1.0",
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index 524ed080..14c2b109 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,14 @@ const appRoutes: Routes = [
path : 'apps/mail',
loadChildren: './main/apps/mail/mail.module#MailModule'
},
+ {
+ path : 'apps/chat',
+ loadChildren: './main/apps/chat/chat.module#ChatModule'
+ },
+ {
+ path : 'apps/calendar',
+ loadChildren: './main/apps/calendar/calendar.module#FuseCalendarModule'
+ },
{
path : '**',
redirectTo: 'apps/dashboards/project'
@@ -55,7 +64,6 @@ const appRoutes: Routes = [
FuseLayoutModule,
- ChatModule,
ProjectModule,
UIPageLayoutsModule
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/components/confirm-dialog/confirm-dialog.component.html b/src/app/core/components/confirm-dialog/confirm-dialog.component.html
new file mode 100644
index 00000000..d7d60191
--- /dev/null
+++ b/src/app/core/components/confirm-dialog/confirm-dialog.component.html
@@ -0,0 +1,6 @@
+
Confirm
+{{confirmMessage}}
+
+
+
+
diff --git a/src/app/core/components/confirm-dialog/confirm-dialog.component.scss b/src/app/core/components/confirm-dialog/confirm-dialog.component.scss
new file mode 100644
index 00000000..e69de29b
diff --git a/src/app/core/components/confirm-dialog/confirm-dialog.component.ts b/src/app/core/components/confirm-dialog/confirm-dialog.component.ts
new file mode 100644
index 00000000..08bb4600
--- /dev/null
+++ b/src/app/core/components/confirm-dialog/confirm-dialog.component.ts
@@ -0,0 +1,21 @@
+import { Component, OnInit } from '@angular/core';
+import { MdDialogRef } from '@angular/material';
+
+@Component({
+ selector : 'fuse-confirm-dialog',
+ templateUrl: './confirm-dialog.component.html',
+ styleUrls : ['./confirm-dialog.component.scss']
+})
+export class FuseConfirmDialogComponent implements OnInit
+{
+ public confirmMessage: string;
+
+ constructor(public dialogRef: MdDialogRef)
+ {
+ }
+
+ ngOnInit()
+ {
+ }
+
+}
diff --git a/src/app/core/modules/shared.module.ts b/src/app/core/modules/shared.module.ts
index 3111a9bc..bfb98650 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';
@@ -13,22 +13,27 @@ import {
FuseMdSidenavTogglerDirective
} from '../directives/md-sidenav-helper/md-sidenav-helper.directive';
import { FusePipesModule } from '../pipes/pipes.module';
+import { ColorPickerModule } from 'ngx-color-picker';
+import { FuseConfirmDialogComponent } from '../components/confirm-dialog/confirm-dialog.component';
@NgModule({
- declarations: [
+ declarations : [
FuseMdSidenavHelperDirective,
- FuseMdSidenavTogglerDirective
+ FuseMdSidenavTogglerDirective,
+ FuseConfirmDialogComponent
],
- imports : [
+ imports : [
FlexLayoutModule,
MaterialModule,
NgxDatatableModule,
CommonModule,
FormsModule,
FusePipesModule,
- PerfectScrollbarModule
+ PerfectScrollbarModule,
+ ReactiveFormsModule,
+ ColorPickerModule
],
- exports : [
+ exports : [
FlexLayoutModule,
MaterialModule,
NgxDatatableModule,
@@ -37,8 +42,11 @@ import { FusePipesModule } from '../pipes/pipes.module';
FuseMdSidenavHelperDirective,
FuseMdSidenavTogglerDirective,
FusePipesModule,
- PerfectScrollbarModule
- ]
+ PerfectScrollbarModule,
+ ReactiveFormsModule,
+ ColorPickerModule
+ ],
+ entryComponents: [FuseConfirmDialogComponent]
})
export class SharedModule
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 613a8514..ebcb5090 100644
--- a/src/app/core/pipes/pipes.module.ts
+++ b/src/app/core/pipes/pipes.module.ts
@@ -3,18 +3,22 @@ import { NgModule } from '@angular/core';
import { KeysPipe } from './keys.pipe';
import { GetByIdPipe } from './getById.pipe';
import { HtmlToPlaintextPipe } from './htmlToPlaintext.pipe';
+import { FilterPipe } from './filter.pipe';
@NgModule({
declarations: [
KeysPipe,
GetByIdPipe,
- HtmlToPlaintextPipe
+ HtmlToPlaintextPipe,
+ FilterPipe
+
],
imports : [],
exports : [
KeysPipe,
GetByIdPipe,
- HtmlToPlaintextPipe
+ HtmlToPlaintextPipe,
+ FilterPipe
]
})
diff --git a/src/app/core/scss/core.scss b/src/app/core/scss/core.scss
index 371c5748..31b2ae0f 100644
--- a/src/app/core/scss/core.scss
+++ b/src/app/core/scss/core.scss
@@ -27,12 +27,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/calendar.ts b/src/app/fuse-fake-db/calendar.ts
new file mode 100644
index 00000000..fab7bc83
--- /dev/null
+++ b/src/app/fuse-fake-db/calendar.ts
@@ -0,0 +1,97 @@
+import {
+ startOfDay,
+ endOfDay,
+ subDays,
+ addDays,
+ endOfMonth,
+ isSameDay,
+ isSameMonth,
+ addHours
+} from 'date-fns';
+
+export class CalendarFakeDb
+{
+
+ public static data = [
+ {
+ id : 'events',
+ data: [
+ {
+ start : subDays(startOfDay(new Date()), 1),
+ end : addDays(new Date(), 1),
+ title : 'A 3 day event',
+ allDay : false,
+ color : {
+ primary : '#ad2121',
+ secondary: '#FAE3E3'
+ },
+ resizable: {
+ beforeStart: true,
+ afterEnd : true
+ },
+ draggable: true,
+ meta : {
+ location: 'Los Angeles',
+ notes : 'Eos eu verear adipiscing, ex ornatus denique iracundia sed, quodsi oportere appellantur an pri.'
+ }
+ },
+ {
+ start : startOfDay(new Date()),
+ title : 'An event with no end date',
+ allDay : false,
+ color : {
+ primary : '#e3bc08',
+ secondary: '#FDF1BA'
+ },
+ resizable: {
+ beforeStart: true,
+ afterEnd : true
+ },
+ draggable: true,
+ meta : {
+ location: 'Los Angeles',
+ notes : 'Eos eu verear adipiscing, ex ornatus denique iracundia sed, quodsi oportere appellantur an pri.'
+ }
+ },
+ {
+ start : subDays(endOfMonth(new Date()), 3),
+ end : addDays(endOfMonth(new Date()), 3),
+ title : 'A long event that spans 2 months',
+ allDay : false,
+ color : {
+ primary : '#1e90ff',
+ secondary: '#D1E8FF'
+ },
+ resizable: {
+ beforeStart: true,
+ afterEnd : true
+ },
+ draggable: true,
+ meta : {
+ location: 'Los Angeles',
+ notes : 'Eos eu verear adipiscing, ex ornatus denique iracundia sed, quodsi oportere appellantur an pri.'
+ }
+ },
+ {
+ start : addHours(startOfDay(new Date()), 2),
+ end : new Date(),
+ title : 'A draggable and resizable event',
+ allDay : false,
+ color : {
+ primary : '#e3bc08',
+ secondary: '#FDF1BA'
+ },
+ resizable: {
+ beforeStart: true,
+ afterEnd : true
+ },
+ draggable: true,
+ meta : {
+ location: 'Los Angeles',
+ notes : 'Eos eu verear adipiscing, ex ornatus denique iracundia sed, quodsi oportere appellantur an pri.'
+ }
+ }
+ ]
+ }
+ ];
+}
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 552e0cb8..bc80d113 100644
--- a/src/app/fuse-fake-db/fuse-fake-db.service.ts
+++ b/src/app/fuse-fake-db/fuse-fake-db.service.ts
@@ -1,15 +1,31 @@
import { InMemoryDbService } from 'angular-in-memory-web-api';
import { MailFakeDb } from './mail';
+import { ChatFakeDb } from './chat';
+import { CalendarFakeDb } from './calendar';
+import {
+ startOfDay,
+ endOfDay,
+ subDays,
+ addDays,
+ endOfMonth,
+ isSameDay,
+ isSameMonth,
+ addHours
+} from 'date-fns';
export class FuseFakeDbService implements InMemoryDbService
{
createDb()
{
return {
- 'mail-mails' : MailFakeDb.mails,
- 'mail-folders': MailFakeDb.folders,
- 'mail-filters': MailFakeDb.filters,
- 'mail-labels' : MailFakeDb.labels
+ 'mail-mails' : MailFakeDb.mails,
+ 'mail-folders' : MailFakeDb.folders,
+ 'mail-filters' : MailFakeDb.filters,
+ 'mail-labels' : MailFakeDb.labels,
+ 'chat-contacts': ChatFakeDb.contacts,
+ 'chat-chats' : ChatFakeDb.chats,
+ 'chat-user' : ChatFakeDb.user,
+ 'calendar' : CalendarFakeDb.data
};
}
diff --git a/src/app/main/apps/calendar/calendar.component.html b/src/app/main/apps/calendar/calendar.component.html
new file mode 100644
index 00000000..efaee667
--- /dev/null
+++ b/src/app/main/apps/calendar/calendar.component.html
@@ -0,0 +1,125 @@
+
diff --git a/src/app/main/apps/calendar/calendar.component.scss b/src/app/main/apps/calendar/calendar.component.scss
new file mode 100644
index 00000000..6e0d17bf
--- /dev/null
+++ b/src/app/main/apps/calendar/calendar.component.scss
@@ -0,0 +1,303 @@
+@import "src/app/core/scss/fuse";
+@import "node_modules/angular-calendar/scss/angular-calendar";
+
+.cal-month-view {
+
+ .cal-header {
+
+ .cal-cell {
+ font-weight: 500;
+ }
+ }
+
+ .cal-day-cell {
+
+ &.cal-open {
+ @include mat-elevation(3);
+ }
+ }
+
+ .cal-open-day-events {
+ background: whitesmoke;
+ box-shadow: inset 0 0 15px 0 rgba(0, 0, 0, 0.13);
+ padding: 0;
+ display: flex;
+ flex-direction: column;
+
+ > div {
+ padding: 0 16px;
+ margin: 8px 16px;
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ justify-content: start;
+ background-color: white;
+ @include mat-elevation(1);
+ transition: box-shadow 300ms ease;
+
+ &:first-of-type {
+ margin-top: 24px;
+ }
+
+ &:last-of-type {
+ margin-bottom: 24px;
+ }
+
+ &:hover {
+ @include mat-elevation(3);
+ }
+
+ .cal-event {
+ top: 0;
+ margin: 0;
+ }
+
+ mwl-calendar-event-title {
+ flex: 1;
+
+ .cal-event-title {
+ display: block;
+ padding: 21px 24px;
+ line-height: 1;
+ text-decoration: none;
+ color: black;
+ }
+ }
+
+ mwl-calendar-event-actions {
+
+ .cal-event-actions {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+
+ .cal-event-action {
+ display: block;
+ line-height: 1;
+ padding: 8px;
+ }
+ }
+ }
+
+ }
+ }
+}
+
+.cal-week-view {
+
+ .cal-header > b {
+ font-weight: 500;
+ }
+
+ .cal-event {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+
+ mwl-calendar-event-title {
+ display: block;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ flex: 1;
+ order: 0;
+
+ .cal-event-title {
+ display: block;
+ //padding: 21px 24px;
+ line-height: 1;
+ text-decoration: none;
+ color: black;
+ }
+ }
+
+ mwl-calendar-event-actions {
+ order: 1;
+
+ .cal-event-actions {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+
+ .cal-event-action {
+ display: block;
+ line-height: 1;
+ padding: 8px;
+ }
+ }
+ }
+ }
+}
+
+.cal-day-view {
+
+ .cal-time {
+ font-weight: 500;
+ }
+
+ .cal-event {
+ display: flex;
+ flex-direction: row;
+
+ mwl-calendar-event-title {
+ display: block;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ flex: 1;
+ order: 0;
+
+ .cal-event-title {
+ display: block;
+ height: 26px;
+ line-height: 26px;
+ text-decoration: none;
+ color: black;
+ }
+ }
+
+ mwl-calendar-event-actions {
+ order: 1;
+
+ .cal-event-actions {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+
+ .cal-event-action {
+ display: block;
+ line-height: 1;
+ padding: 4px;
+ }
+ }
+ }
+ }
+}
+
+#calendar {
+ background: #FFFFFF;
+ height: 100%;
+
+ .header {
+ height: 200px;
+ min-height: 200px;
+ max-height: 200px;
+ padding: 24px;
+ position: relative;
+ background-size: 100% auto;
+ background-position: 0 50%;
+ background-repeat: no-repeat;
+ background-color: #FAFAFA;
+ color: #FFFFFF;
+ padding-bottom: 16px;
+
+ &:before {
+ content: '';
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ z-index: 1;
+ background: rgba(0, 0, 0, 0.45);
+ }
+
+ &.Jan {
+ background-image: url('/assets/images/backgrounds/january.jpg');
+ background-position: 0 45%;
+ }
+ &.Feb {
+ background-image: url('/assets/images/backgrounds/february.jpg');
+ background-position: 0 50%;
+ }
+ &.Mar {
+ background-image: url('/assets/images/backgrounds/march.jpg');
+ background-position: 0 45%;
+ }
+ &.Apr {
+ background-image: url('/assets/images/backgrounds/april.jpg');
+ background-position: 0 48%;
+ }
+ &.May {
+ background-image: url('/assets/images/backgrounds/may.jpg');
+ background-position: 0 47%;
+ }
+ &.Jun {
+ background-image: url('/assets/images/backgrounds/june.jpg');
+ background-position: 0 48%;
+ }
+ &.Jul {
+ background-image: url('/assets/images/backgrounds/july.jpg');
+ background-position: 0 3%;
+ }
+ &.Aug {
+ background-image: url('/assets/images/backgrounds/august.jpg');
+ background-position: 0 61%;
+ }
+ &.Sep {
+ background-image: url('/assets/images/backgrounds/september.jpg');
+ background-position: 0 58%;
+ }
+ &.Oct {
+ background-image: url('/assets/images/backgrounds/october.jpg');
+ background-position: 0 50%;
+ }
+ &.Nov {
+ background-image: url('/assets/images/backgrounds/november.jpg');
+ background-position: 0 46%;
+ }
+ &.Dec {
+ background-image: url('/assets/images/backgrounds/december.jpg');
+ background-position: 0 43%;
+ }
+
+ .header-content {
+ height: 100%;
+
+ .header-top {
+ position: relative;
+ z-index: 2;
+
+ .logo {
+
+ .logo-icon {
+ margin-right: 16px;
+ }
+
+ .logo-text {
+ font-size: 24px;
+ }
+ }
+ }
+
+ .header-bottom {
+ position: relative;
+ z-index: 2;
+
+ .title {
+ font-size: 20px;
+ min-width: 160px;
+ text-align: center;
+ font-weight: 500;
+ }
+ }
+ }
+
+ .add-event-button {
+ position: absolute;
+ right: 18px;
+ bottom: -32px;
+ z-index: 10;
+ }
+
+ md-icon {
+ color: #FFFFFF;
+ }
+ }
+
+ .content {
+ flex: 1;
+ overflow: auto;
+ padding: 24px;
+ }
+}
diff --git a/src/app/main/apps/calendar/calendar.component.ts b/src/app/main/apps/calendar/calendar.component.ts
new file mode 100644
index 00000000..b208c028
--- /dev/null
+++ b/src/app/main/apps/calendar/calendar.component.ts
@@ -0,0 +1,268 @@
+import { startOfDay, endOfDay, subDays, addDays, endOfMonth, isSameDay, isSameMonth, addHours } from 'date-fns';
+import { ChangeDetectionStrategy, Component, OnInit, TemplateRef, ViewChild, ViewEncapsulation } from '@angular/core';
+import { Subject } from 'rxjs/Subject';
+import { MdDialog, MdDialogRef } from '@angular/material';
+import { EventFormDialogComponent } from './event-form/event-form.component';
+import { FormGroup } from '@angular/forms';
+import { CalendarEventModel } from './event.model';
+import { CalendarService } from './calendar.service';
+import {
+ CalendarEvent,
+ CalendarEventAction,
+ CalendarEventTimesChangedEvent,
+ CalendarMonthViewDay
+} from 'angular-calendar';
+import { FuseConfirmDialogComponent } from '../../../core/components/confirm-dialog/confirm-dialog.component';
+
+@Component({
+ selector : 'fuse-calendar',
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ templateUrl : './calendar.component.html',
+ styleUrls : ['./calendar.component.scss'],
+ encapsulation : ViewEncapsulation.None
+})
+export class CalendarComponent implements OnInit
+{
+ @ViewChild('dialogContent') dialogContent: TemplateRef;
+
+ view: string;
+
+ viewDate: Date;
+
+ events: CalendarEvent[];
+
+ public actions: CalendarEventAction[];
+
+ activeDayIsOpen: boolean;
+
+ refresh: Subject = new Subject();
+
+ dialogRef: any;
+
+ confirmDialogRef: MdDialogRef;
+
+ selectedDay: any;
+
+ constructor(public dialog: MdDialog,
+ public calendarService: CalendarService)
+ {
+ this.view = 'month';
+ this.viewDate = new Date();
+ this.activeDayIsOpen = true;
+ this.selectedDay = {date: startOfDay(new Date())};
+
+ this.actions = [
+ {
+ label : 'edit',
+ onClick: ({event}: { event: CalendarEvent }): void => {
+ this.editEvent('edit', event);
+ }
+ },
+ {
+ label : 'delete',
+ onClick: ({event}: { event: CalendarEvent }): void => {
+ this.deleteEvent(event);
+ }
+ }
+ ];
+
+ /**
+ * Get events from service/server
+ */
+ this.setEvents();
+ }
+
+ ngOnInit()
+ {
+ /**
+ * Watch re-render-refresh for updating db
+ */
+ this.refresh.subscribe(updateDB => {
+ // console.warn('REFRESH');
+ if ( updateDB )
+ {
+ // console.warn('UPDATE DB');
+ this.calendarService.updateEvents(this.events);
+ }
+ });
+
+ this.calendarService.onEventsUpdated.subscribe(events => {
+ this.setEvents();
+ this.refresh.next();
+ });
+ }
+
+ setEvents()
+ {
+ this.events = this.calendarService.events.map(item => {
+ item.actions = this.actions;
+ return new CalendarEventModel(item);
+ });
+ }
+
+ /**
+ * Before View Renderer
+ * @param {any} header
+ * @param {any} body
+ */
+ beforeMonthViewRender({header, body})
+ {
+ // console.info('beforeMonthViewRender');
+ /**
+ * Get the selected day
+ */
+ const _selectedDay = body.find((_day) => {
+ return _day.date.getTime() === this.selectedDay.date.getTime();
+ });
+
+ if ( _selectedDay )
+ {
+ /**
+ * Set selectedday style
+ * @type {string}
+ */
+ _selectedDay.cssClass = 'mat-elevation-z3';
+ }
+
+ }
+
+
+ /**
+ * Day clicked
+ * @param {MonthViewDay} day
+ */
+ dayClicked(day: CalendarMonthViewDay): void
+ {
+ const date: Date = day.date;
+ const events: CalendarEvent[] = day.events;
+
+ if ( isSameMonth(date, this.viewDate) )
+ {
+ if ( (isSameDay(this.viewDate, date) && this.activeDayIsOpen === true) || events.length === 0 )
+ {
+ this.activeDayIsOpen = false;
+ }
+ else
+ {
+ this.activeDayIsOpen = true;
+ this.viewDate = date;
+ }
+ }
+ this.selectedDay = day;
+ this.refresh.next();
+ }
+
+ /**
+ * Event times changed
+ * Event dropped or resized
+ * @param {CalendarEvent} event
+ * @param {Date} newStart
+ * @param {Date} newEnd
+ */
+ eventTimesChanged({event, newStart, newEnd}: CalendarEventTimesChangedEvent): void
+ {
+ event.start = newStart;
+ event.end = newEnd;
+ // console.warn('Dropped or resized', event);
+ this.refresh.next(true);
+ }
+
+ /**
+ * Delete Event
+ * @param event
+ */
+ deleteEvent(event)
+ {
+ this.confirmDialogRef = this.dialog.open(FuseConfirmDialogComponent, {
+ disableClose: false
+ });
+
+ this.confirmDialogRef.componentInstance.confirmMessage = 'Are you sure you want to delete?';
+
+ this.confirmDialogRef.afterClosed().subscribe(result => {
+ if ( result )
+ {
+ const eventIndex = this.events.indexOf(event);
+ this.events.splice(eventIndex, 1);
+ this.refresh.next(true);
+ }
+ this.confirmDialogRef = null;
+ });
+
+ }
+
+ /**
+ * Edit Event
+ * @param {string} action
+ * @param {CalendarEvent} event
+ */
+ editEvent(action: string, event: CalendarEvent)
+ {
+ const eventIndex = this.events.indexOf(event);
+
+ this.dialogRef = this.dialog.open(EventFormDialogComponent, {
+ panelClass: 'event-form-dialog',
+ data : {
+ event : event,
+ action: action
+ }
+ });
+
+ this.dialogRef.afterClosed()
+ .subscribe(response => {
+ if ( !response )
+ {
+ return;
+ }
+ const actionType: string = response[0];
+ const formData: FormGroup = response[1];
+ switch ( actionType )
+ {
+ /**
+ * Save
+ */
+ case 'save':
+
+ this.events[eventIndex] = Object.assign(this.events[eventIndex], formData.getRawValue());
+ this.refresh.next(true);
+
+ break;
+ /**
+ * Delete
+ */
+ case 'delete':
+
+ this.deleteEvent(event);
+
+ break;
+ }
+ });
+ }
+
+ /**
+ * Add Event
+ */
+ addEvent(): void
+ {
+ this.dialogRef = this.dialog.open(EventFormDialogComponent, {
+ panelClass: 'event-form-dialog',
+ data : {
+ action: 'new',
+ date : this.selectedDay.date
+ }
+ });
+ this.dialogRef.afterClosed()
+ .subscribe((response: FormGroup) => {
+ if ( !response )
+ {
+ return;
+ }
+ const newEvent = response.getRawValue();
+ newEvent.actions = this.actions;
+ this.events.push(newEvent);
+ this.refresh.next(true);
+ });
+ }
+}
+
+
diff --git a/src/app/main/apps/calendar/calendar.module.ts b/src/app/main/apps/calendar/calendar.module.ts
new file mode 100644
index 00000000..ca59bee0
--- /dev/null
+++ b/src/app/main/apps/calendar/calendar.module.ts
@@ -0,0 +1,37 @@
+import { NgModule } from '@angular/core';
+import { SharedModule } from '../../../core/modules/shared.module';
+import { RouterModule, Routes } from '@angular/router';
+import { CalendarComponent } from './calendar.component';
+import { CalendarService } from './calendar.service';
+import { CalendarModule } from 'angular-calendar';
+import { EventFormDialogComponent } from './event-form/event-form.component';
+import { EventDetailDialogComponent } from './event-detail/event-detail.component';
+
+const routes: Routes = [
+ {
+ path : '**', component: CalendarComponent, children: [],
+ resolve: {
+ chat: CalendarService
+ }
+ }
+];
+
+@NgModule({
+ imports : [
+ SharedModule,
+ RouterModule.forChild(routes),
+ CalendarModule.forRoot()
+ ],
+ declarations : [
+ CalendarComponent,
+ EventFormDialogComponent,
+ EventDetailDialogComponent,
+ ],
+ providers : [
+ CalendarService
+ ],
+ entryComponents: [EventFormDialogComponent, EventDetailDialogComponent]
+})
+export class FuseCalendarModule
+{
+}
diff --git a/src/app/main/apps/calendar/calendar.service.ts b/src/app/main/apps/calendar/calendar.service.ts
new file mode 100644
index 00000000..c765e74a
--- /dev/null
+++ b/src/app/main/apps/calendar/calendar.service.ts
@@ -0,0 +1,55 @@
+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';
+
+@Injectable()
+export class CalendarService implements Resolve
+{
+ events: any;
+ onEventsUpdated = new Subject();
+
+ constructor(private http: Http)
+ {
+
+ }
+
+ resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable | Promise | any
+ {
+ return new Promise((resolve, reject) => {
+ Promise.all([
+ this.getEvents()
+ ]).then(
+ ([events]: [any]) => {
+ resolve();
+ },
+ reject
+ );
+ });
+ }
+
+ getEvents()
+ {
+ return new Promise((resolve, reject) => {
+
+ this.http.get('api/calendar/events')
+ .subscribe(response => {
+ this.events = response.json().data.data;
+ this.onEventsUpdated.next(this.events);
+ resolve(this.events);
+ }, reject);
+ });
+ }
+
+ updateEvents(events)
+ {
+ return new Promise((resolve, reject) => {
+ this.http.post('api/calendar/events', {id: 'events', data: [...events]})
+ .subscribe(response => {
+ this.getEvents();
+ }, reject);
+ });
+ }
+
+}
diff --git a/src/app/main/apps/calendar/event-detail/event-detail.component.html b/src/app/main/apps/calendar/event-detail/event-detail.component.html
new file mode 100644
index 00000000..026b9043
--- /dev/null
+++ b/src/app/main/apps/calendar/event-detail/event-detail.component.html
@@ -0,0 +1,3 @@
+
+ event-detail works!
+
diff --git a/src/app/main/apps/calendar/event-detail/event-detail.component.scss b/src/app/main/apps/calendar/event-detail/event-detail.component.scss
new file mode 100644
index 00000000..e69de29b
diff --git a/src/app/main/apps/calendar/event-detail/event-detail.component.ts b/src/app/main/apps/calendar/event-detail/event-detail.component.ts
new file mode 100644
index 00000000..e9c91f28
--- /dev/null
+++ b/src/app/main/apps/calendar/event-detail/event-detail.component.ts
@@ -0,0 +1,19 @@
+import { Component, OnInit } from '@angular/core';
+
+@Component({
+ selector : 'fuse-calendar-event-detail-dialog',
+ templateUrl: './event-detail.component.html',
+ styleUrls : ['./event-detail.component.scss']
+})
+export class EventDetailDialogComponent implements OnInit
+{
+
+ constructor()
+ {
+ }
+
+ ngOnInit()
+ {
+ }
+
+}
diff --git a/src/app/main/apps/calendar/event-form/event-form.component.html b/src/app/main/apps/calendar/event-form/event-form.component.html
new file mode 100644
index 00000000..ab1141a2
--- /dev/null
+++ b/src/app/main/apps/calendar/event-form/event-form.component.html
@@ -0,0 +1,139 @@
+
+
+ {{dialogTitle}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/app/main/apps/calendar/event-form/event-form.component.scss b/src/app/main/apps/calendar/event-form/event-form.component.scss
new file mode 100644
index 00000000..ce618e53
--- /dev/null
+++ b/src/app/main/apps/calendar/event-form/event-form.component.scss
@@ -0,0 +1,12 @@
+.event-form-dialog {
+ .mat-dialog-container {
+ padding: 0;
+ max-width: 720px;
+ width: 720px;
+ }
+}
+
+:host {
+ display: flex;
+ flex-direction: column;
+}
diff --git a/src/app/main/apps/calendar/event-form/event-form.component.ts b/src/app/main/apps/calendar/event-form/event-form.component.ts
new file mode 100644
index 00000000..ccf7f713
--- /dev/null
+++ b/src/app/main/apps/calendar/event-form/event-form.component.ts
@@ -0,0 +1,64 @@
+import { Component, Inject, OnInit, ViewEncapsulation } from '@angular/core';
+import { MD_DIALOG_DATA, MdDialogRef } from '@angular/material';
+import { CalendarEvent } from 'angular-calendar';
+import { FormBuilder, FormControl, FormGroup } from '@angular/forms';
+import 'rxjs/Rx';
+import { CalendarEventModel } from '../event.model';
+
+@Component({
+ selector : 'fuse-calendar-event-form-dialog',
+ templateUrl : './event-form.component.html',
+ styleUrls : ['./event-form.component.scss'],
+ encapsulation: ViewEncapsulation.None
+})
+
+export class EventFormDialogComponent implements OnInit
+{
+ event: CalendarEvent;
+ dialogTitle: string;
+ eventForm: FormGroup;
+ action: string;
+
+ constructor(public dialogRef: MdDialogRef,
+ @Inject(MD_DIALOG_DATA) private data: any,
+ private formBuilder: FormBuilder)
+ {
+ this.event = data.event;
+ this.action = data.action;
+
+ if ( this.action === 'edit' )
+ {
+ this.dialogTitle = this.event.title;
+ }
+ else
+ {
+ this.dialogTitle = 'New Event';
+ this.event = new CalendarEventModel({start: data.date, end: data.date});
+ }
+
+ this.eventForm = this.createEventForm();
+ }
+
+ ngOnInit()
+ {
+ }
+
+ createEventForm()
+ {
+ return new FormGroup({
+ title : new FormControl(this.event.title),
+ start : new FormControl(this.event.start),
+ end : new FormControl(this.event.end),
+ allDay: new FormControl(this.event.allDay),
+ color : this.formBuilder.group({
+ primary : new FormControl(this.event.color.primary),
+ secondary: new FormControl(this.event.color.secondary)
+ }),
+ meta :
+ this.formBuilder.group({
+ location: new FormControl(this.event.meta.location),
+ notes : new FormControl(this.event.meta.notes)
+ })
+ });
+ }
+}
diff --git a/src/app/main/apps/calendar/event.model.ts b/src/app/main/apps/calendar/event.model.ts
new file mode 100644
index 00000000..67008724
--- /dev/null
+++ b/src/app/main/apps/calendar/event.model.ts
@@ -0,0 +1,75 @@
+import {
+ CalendarEventAction
+} from 'angular-calendar';
+
+import {
+ startOfDay,
+ endOfDay,
+ subDays,
+ addDays,
+ endOfMonth,
+ isSameDay,
+ isSameMonth,
+ addHours
+} from 'date-fns';
+// import { CalendarEvent } from 'calendar-utils/dist/calendar-utils';
+
+/*
+export interface EventAction
+{
+ label: string;
+ cssClass?: string;
+
+ onClick({event}: {
+ event: CalendarEvent;
+ }): any;
+}*/
+
+export class CalendarEventModel
+{
+ start: Date;
+ end?: Date;
+ title: string;
+ color: {
+ primary: string;
+ secondary: string;
+ };
+ actions?: CalendarEventAction[];
+ allDay?: boolean;
+ cssClass?: string;
+ resizable?: {
+ beforeStart?: boolean;
+ afterEnd?: boolean;
+ };
+ draggable?: boolean;
+ meta?: {
+ location: string,
+ notes: string
+ };
+
+ constructor(data?)
+ {
+ data = data || {};
+ this.start = new Date(data.start) || startOfDay(new Date());
+ this.end = new Date(data.end) || endOfDay(new Date());
+ this.title = data.title || '';
+ this.color = {
+ primary : data.color && data.color.primary || '#1e90ff',
+ secondary: data.color && data.color.secondary || '#D1E8FF'
+ };
+ this.draggable = data.draggable || true;
+ this.resizable = {
+ beforeStart: data.resizable && data.resizable.beforeStart || true,
+ afterEnd : data.resizable && data.resizable.afterEnd || true
+ };
+ this.actions = data.actions || [];
+ this.allDay = data.allDay || false;
+ this.cssClass = data.cssClass || '';
+ this.meta = {
+ location: data.meta && data.meta.location || '',
+ notes : data.meta && data.meta.notes || ''
+ };
+ }
+
+
+}
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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{{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..4cb3e4e5 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 : '**', 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..78638753
--- /dev/null
+++ b/src/app/main/apps/chat/chat.service.ts
@@ -0,0 +1,260 @@
+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 = {
+ contactId : contactId,
+ id : chatId,
+ lastMessageTime: '2017-02-18T10:30:18.931Z',
+ name : contact.name,
+ unread : null,
+ };
+
+ /**
+ * 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(newUserData => {
+
+ /**
+ * 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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
0">
+ Chats
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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;
+ });
+ }
+
+}