# Conflicts:
#	package.json
#	src/app/app.module.ts
This commit is contained in:
Sercan Yemen 2017-07-26 12:45:28 +03:00
commit 7c770d6bf0
59 changed files with 3925 additions and 40 deletions

42
package-lock.json generated
View File

@ -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",

View File

@ -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",

View File

@ -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

View File

@ -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'))
]);
}

View File

@ -0,0 +1,6 @@
<h1 md-dialog-title>Confirm</h1>
<div md-dialog-content>{{confirmMessage}}</div>
<div md-dialog-actions class="pt-24">
<button md-raised-button class="mat-accent mr-16" (click)="dialogRef.close(true)">Confirm</button>
<button md-button (click)="dialogRef.close(false)">Cancel</button>
</div>

View File

@ -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<FuseConfirmDialogComponent>)
{
}
ngOnInit()
{
}
}

View File

@ -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

View File

@ -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));
}
}

View File

@ -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
]
})

View File

@ -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";

View File

@ -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;
}

View File

@ -2,6 +2,10 @@
box-sizing: border-box;
}
html {
}
html, body {
margin: 0;
width: 100%;

View File

@ -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;
}
}
}

View File

@ -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
/*----------------------------------------------------------------*/

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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.'
}
}
]
}
];
}

View File

@ -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 doesnt 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': 'Im having breakfast right now, cant 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': 'Its 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': 'Its 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': 'Its 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': 'Its 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': 'Im having breakfast right now, cant 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'
}
]
}
];
}

View File

@ -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
};
}

View File

@ -0,0 +1,125 @@
<div id="calendar" class="page-layout simple fullwidth" fxLayout="column">
<!-- HEADER -->
<div class="header" [ngClass]="viewDate | date:'MMM'">
<div class="header-content" fxLayout="column" fxLayoutAlign="space-between">
<div class="header-top" fxLayout="row" fxLayoutAlign="space-between center" fxLayout.xs="column">
<div class="logo" fxLayout="row" fxLayoutAlign="start center">
<md-icon class="logo-icon">today</md-icon>
<span class="logo-text">Calendar</span>
</div>
<!-- TOOLBAR -->
<div class="toolbar" fxLayout="row" fxLayoutAlign="start center">
<button md-button class="mat-icon-button" aria-label="Search" md-tooltip="search">
<md-icon>search</md-icon>
</button>
<button md-button class="mat-icon-button"
mwlCalendarToday
[(viewDate)]="viewDate"
(viewDateChange)="selectedDay = {date:$event}"
aria-label="Today" md-tooltip="today">
<!--(click)="selectedDay = viewDate"-->
<md-icon>today</md-icon>
</button>
<button md-button class="mat-icon-button" (click)="view='day'"
aria-label="Day" md-tooltip="day">
<md-icon>view_day</md-icon>
</button>
<button md-button class="mat-icon-button" (click)="view='week'"
aria-label="Week" md-tooltip="week">
<md-icon>view_week</md-icon>
</button>
<button md-button class="mat-icon-button" (click)="view='month'"
aria-label="Month" md-tooltip="month">
<md-icon>view_module</md-icon>
</button>
</div>
</div>
<!-- / TOOLBAR -->
<!-- HEADER BOTTOM -->
<div class="header-bottom" fxLayout="row" fxLayoutAlign="center center">
<button md-button class="mat-icon-button arrow"
mwlCalendarPreviousView
[view]="view"
[(viewDate)]="viewDate"
(viewDateChange)="selectedDay = {date:$event}"
aria-label="Previous">
<md-icon>chevron_left</md-icon>
</button>
<div class="title">
{{ viewDate | calendarDate:(view + 'ViewTitle'):'en' }}
</div>
<button md-button class="mat-icon-button arrow"
mwlCalendarNextView
[view]="view"
[(viewDate)]="viewDate"
(viewDateChange)="selectedDay = {date:$event}"
aria-label="Next">
<md-icon>chevron_right</md-icon>
</button>
</div>
<!-- / HEADER BOTTOM -->
</div>
<!-- ADD EVENT BUTTON -->
<button md-fab class="add-event-button md-warn" (click)="addEvent($event)" aria-label="Add event">
<md-icon>add</md-icon>
</button>
<!-- / ADD EVENT BUTTON -->
</div>
<!-- / HEADER -->
<!-- CONTENT -->
<div class="content" perfect-scrollbar>
<div [ngSwitch]="view">
<mwl-calendar-month-view
*ngSwitchCase="'month'"
[viewDate]="viewDate"
[events]="events"
[refresh]="refresh"
[activeDayIsOpen]="activeDayIsOpen"
(dayClicked)="dayClicked($event.day)"
(eventClicked)="editEvent('edit', $event.event)"
(eventTimesChanged)="eventTimesChanged($event)"
(beforeViewRender)="beforeMonthViewRender($event)">
</mwl-calendar-month-view>
<mwl-calendar-week-view
*ngSwitchCase="'week'"
[viewDate]="viewDate"
(viewDateChange)="selectedDay = {date:$event}"
[events]="events"
[refresh]="refresh"
(dayClicked)="dayClicked($event.day)"
(eventClicked)="editEvent('edit', $event.event)"
(eventTimesChanged)="eventTimesChanged($event)">
</mwl-calendar-week-view>
<mwl-calendar-day-view
*ngSwitchCase="'day'"
[viewDate]="viewDate"
(viewDateChange)="selectedDay = {date:$event}"
[events]="events"
[refresh]="refresh"
(dayClicked)="dayClicked($event.day)"
(eventClicked)="editEvent('edit', $event.event)"
(eventTimesChanged)="eventTimesChanged($event)">
</mwl-calendar-day-view>
</div>
</div>
<!-- / CONTENT -->
</div>

View File

@ -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;
}
}

View File

@ -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<any>;
view: string;
viewDate: Date;
events: CalendarEvent[];
public actions: CalendarEventAction[];
activeDayIsOpen: boolean;
refresh: Subject<any> = new Subject();
dialogRef: any;
confirmDialogRef: MdDialogRef<FuseConfirmDialogComponent>;
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 : '<i class="material-icons s-16">edit</i>',
onClick: ({event}: { event: CalendarEvent }): void => {
this.editEvent('edit', event);
}
},
{
label : '<i class="material-icons s-16">delete</i>',
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);
});
}
}

View File

@ -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
{
}

View File

@ -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<any>
{
events: any;
onEventsUpdated = new Subject<any>();
constructor(private http: Http)
{
}
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<any> | Promise<any> | 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);
});
}
}

View File

@ -0,0 +1,3 @@
<p>
event-detail works!
</p>

View File

@ -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()
{
}
}

View File

@ -0,0 +1,139 @@
<md-toolbar md-dialog-title class="mat-accent m-0">
<div fxFlex fxLayout="row" fxLayoutAlign="space-between center">
<span class="title dialog-title">{{dialogTitle}}</span>
<button md-button class="mat-icon-button"
(click)="dialogRef.close()"
aria-label="Close dialog">
<md-icon>close</md-icon>
</button>
</div>
</md-toolbar>
<div md-dialog-content class="p-24 m-0" perfect-scrollbar>
<form name="eventForm" [formGroup]="eventForm" class="event-form" fxLayout="column" fxFlex>
<md-input-container>
<input mdInput
name="title"
formControlName="title"
placeholder="Title"
required>
</md-input-container>
<div class="py-16" fxFlex="1 0 auto" fxLayout="row">
<md-slide-toggle
name="allDay"
formControlName="allDay"
class="mr-24"
aria-label="All day">
All Day
</md-slide-toggle>
</div>
<div class="py-16" fxFlex="1 0 auto" fxLayout="row" formGroupName="color">
<md-input-container class="mr-24">
<input mdInput
name="primary color"
formControlName="primary"
placeholder="Primary color"
[(colorPicker)]="event.color.primary"
[style.background]="event.color.primary"
(colorPickerChange)="event.color.primary = $event; eventForm.patchValue({color:{primary:$event}})"/>
</md-input-container>
<md-input-container>
<input mdInput
name="secondary color"
formControlName="secondary"
placeholder="Secondary color"
[(colorPicker)]="event.color.secondary"
[style.background]="event.color.secondary"
(colorPickerChange)="event.color.secondary = $event; eventForm.patchValue({color:{secondary:$event}})"/>
</md-input-container>
</div>
<div fxFlex="1 0 auto" fxLayout="row">
<md-input-container class="mr-24">
<input mdInput
name="start"
formControlName="start"
[mdDatepicker]="startDatePicker"
placeholder="Start Date">
<button mdSuffix [mdDatepickerToggle]="startDatePicker"></button>
</md-input-container>
<md-datepicker #startDatePicker></md-datepicker>
<md-input-container class="no-errors-spacer" flex md-no-float>
<input mdInput ng-model="calendarEvent.startTime" placeholder="Start Time">
</md-input-container>
</div>
<div fxFlex="1 0 auto" fxLayout="row">
<md-input-container class="mr-24">
<input mdInput
name="end"
formControlName="end"
[mdDatepicker]="endDatePicker"
placeholder="End Date">
<button mdSuffix [mdDatepickerToggle]="endDatePicker"></button>
</md-input-container>
<md-datepicker #endDatePicker></md-datepicker>
<md-input-container class="no-errors-spacer" flex md-no-float>
<input mdInput ng-model="calendarEvent.endTime" placeholder="End Time">
</md-input-container>
</div>
<md-input-container formGroupName="meta">
<input mdInput
name="location"
formControlName="location"
placeholder="Location">
</md-input-container>
<md-input-container formGroupName="meta">
<textarea mdInput
formControlName="notes"
placeholder="Notes"
md-maxlength="250"
max-rows="4">
</textarea>
</md-input-container>
</form>
</div>
<div md-dialog-actions class="m-0 p-16" fxLayout="row" fxLayoutAlign="space-between center">
<button *ngIf="action !=='edit'"
md-raised-button
(click)="dialogRef.close(eventForm)"
class="save-button mat-accent"
aria-label="SAVE">
SAVE
</button>
<button *ngIf="action ==='edit'"
md-raised-button
(click)="dialogRef.close(['save',eventForm])"
class="save-button mat-accent"
aria-label="SAVE">
SAVE
</button>
<button *ngIf="action ==='edit'"
md-button
class="mat-icon-button"
(click)="dialogRef.close(['delete',eventForm])"
aria-label="Delete"
md-tooltip="Delete">
<md-icon>delete</md-icon>
</button>
</div>

View File

@ -0,0 +1,12 @@
.event-form-dialog {
.mat-dialog-container {
padding: 0;
max-width: 720px;
width: 720px;
}
}
:host {
display: flex;
flex-direction: column;
}

View File

@ -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<EventFormDialogComponent>,
@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)
})
});
}
}

View File

@ -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 || ''
};
}
}

View File

@ -0,0 +1,17 @@
<div fxFlex fxLayout="column" fxLayoutAlign="center center">
<div class="big-circle mat-elevation-z1" fxLayout="column" fxLayoutAlign="center center">
<md-icon class="s-128">chat</md-icon>
</div>
<span class="app-title my-24">Chat App</span>
<span fxHide fxShow.gt-md class="app-message">Select contact to start the chat!..</span>
<button md-raised-button fxHide.gt-md class="" fuseMdSidenavToggler="chat-left-sidenav">
Select contact to start the chat!..
</button>
</div>

View File

@ -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;
}
}

View File

@ -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()
{
}
}

View File

@ -0,0 +1,115 @@
<!-- CHAT -->
<div class="chat" fxFlex fxLayout="column">
<!-- CHAT TOOLBAR -->
<md-toolbar class="chat-toolbar">
<div fxFlex fxLayout="row" fxLayoutAlign="space-between center">
<div fxLayout="row" fxLayoutAlign="start center">
<!-- RESPONSIVE CHATS BUTTON-->
<div md-button fxHide.gt-md class="responsive-chats-button mat-icon-button mr-16"
fuseMdSidenavToggler="chat-left-sidenav"
aria-label="chats button">
<md-icon class="s-36">chat</md-icon>
</div>
<!-- / RESPONSIVE CHATS BUTTON-->
<!-- CHAT CONTACT-->
<div class="chat-contact" fxLayout="row" fxLayoutAlign="start center"
fuseMdSidenavToggler="chat-right-sidenav" (click)="selectContact()">
<div class="avatar-wrapper">
<img [src]="contact.avatar"
class="avatar"
alt="{{contact.name}}"/>
<md-icon class="s-16 status"
[ngClass]="contact.status">
</md-icon>
</div>
<div class="chat-contact-name">
{{contact.name}}
</div>
</div>
<!-- / CHAT CONTACT-->
</div>
<div>
<button md-button class="mat-icon-button" [mdMenuTriggerFor]="contactMenu"
aria-label="more">
<md-icon>more_vert</md-icon>
</button>
<md-menu #contactMenu="mdMenu">
<button md-menu-item fuseMdSidenavToggler="chat-right-sidenav" (click)="selectContact()">
Contact Info
</button>
</md-menu>
</div>
</div>
</md-toolbar>
<!-- / CHAT TOOLBAR -->
<!-- CHAT CONTENT -->
<div id="chat-content" fxFlex perfect-scrollbar>
<!-- CHAT MESSAGES -->
<div class="chat-messages">
<!-- MESSAGE -->
<div fxLayout="row" *ngFor="let message of dialog" class="message-row"
[ngClass]="{'user' :message.who === user.id}">
<img *ngIf="message.who === contact.id"
src="{{contact.avatar}}"
class="avatar"
alt="{{contact.name}}"/>
<img *ngIf="message.who ===user.id" class="avatar" src="{{user.avatar}}">
<div class="bubble">
<div class="message">{{message.message}}</div>
<div class="time secondary-text">{{message.time | date:'medium'}}</div>
</div>
</div>
<!-- / MESSAGE -->
</div>
<!-- CHAT MESSAGES -->
</div>
<!-- / CHAT CONTENT -->
<!-- CHAT FOOTER -->
<div class="chat-footer" fxLayout="row" fxLayoutAlign="center center">
<!-- REPLY FORM -->
<form #replyForm="ngForm"
(ngSubmit)="reply($event)"
(keyup.enter)="reply($event)"
fxFlex class="reply-form"
fxLayout="row"
fxLayoutAlign="start center">
<md-input-container class="" fxFlex floatPlaceholder="never">
<textarea mdInput #replyInput placeholder="Type and hit enter to send message"
ngModel name="message"></textarea>
</md-input-container>
<button md-fab class="" type="submit" aria-label="Send message">
<md-icon>send</md-icon>
</button>
</form>
<!-- / REPLY FORM -->
</div>
<!-- / CHAT FOOTER-->
</div>
<!-- / CHAT -->

View File

@ -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;
}
}
}
}
}

View File

@ -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();
});
}
}

View File

@ -1,3 +1,43 @@
<p>
chat works!
</p>
<div class="page-layout carded fullwidth">
<!-- TOP BACKGROUND -->
<div class="top-bg md-accent-bg"></div>
<!-- / TOP BACKGROUND -->
<!-- CENTER -->
<div class="center">
<!-- CONTENT CARD -->
<div class="content-card">
<md-sidenav-container>
<!-- LEFT SIDENAV -->
<md-sidenav class="sidenav mat-sidenav-opened" align="start" opened="true" mode="side"
fuseMdSidenavHelper="chat-left-sidenav" md-is-locked-open="gt-md">
<fuse-chat-left-sidenav></fuse-chat-left-sidenav>
</md-sidenav>
<!-- / LEFT SIDENAV -->
<!-- CONTENT -->
<fuse-chat-start *ngIf="!selectedChat"></fuse-chat-start>
<fuse-chat-view *ngIf="selectedChat"></fuse-chat-view>
<!-- / CONTENT -->
<!-- RIGHT SIDENAV -->
<md-sidenav class="sidenav mat-sidenav-opened" align="end" opened="false" mode="over"
fuseMdSidenavHelper="chat-right-sidenav">
<fuse-chat-right-sidenav></fuse-chat-right-sidenav>
</md-sidenav>
<!-- / RIGHT SIDENAV -->
</md-sidenav-container>
</div>
<!-- / CONTENT CARD -->
</div>
<!-- / CENTER -->
</div>

View File

@ -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;
}
}
}
}
}

View File

@ -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;
});
}
}

View File

@ -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

View File

@ -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<any>
{
contacts: any[];
chats: any[];
user: any;
onChatSelected = new BehaviorSubject<any>(null);
onContactSelected = new BehaviorSubject<any>(null);
onChatsUpdated = new Subject<any>();
onUserUpdated = new Subject<any>();
onLeftSidenavViewChanged = new Subject<any>();
onRightSidenavViewChanged = new Subject<any>();
constructor(private http: Http)
{
}
/**
* Get chat
* @param contactId
* @returns {Promise<any>}
*/
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<any>}
*/
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<any>}
*/
updateDialog(chatId, dialog): Promise<any>
{
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<any> | Promise<any> | any}
*/
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<any> | Promise<any> | 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<any>}
*/
getContacts(): Promise<any>
{
return new Promise((resolve, reject) => {
this.http.get('api/chat-contacts')
.subscribe(response => {
resolve(response.json().data);
}, reject);
});
}
/**
* Get Chats
* @returns {Promise<any>}
*/
getChats(): Promise<any>
{
return new Promise((resolve, reject) => {
this.http.get('api/chat-chats')
.subscribe(response => {
resolve(response.json().data);
}, reject);
});
}
/**
* Get User
* @returns {Promise<any>}
*/
getUser(): Promise<any>
{
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());
}
}

View File

@ -0,0 +1,183 @@
<!-- SIDENAV HEADER -->
<div class="sidenav-header">
<!-- CHATS TOOLBAR -->
<md-toolbar>
<!-- TOOLBAR TOP -->
<div fxFlex fxLayout="row" fxLayoutAlign="space-between center">
<!-- USER AVATAR WRAPPER -->
<div class="avatar-wrapper">
<!-- USER AVATAR -->
<img (click)="changeLeftSidenavView('user')"
src="{{user.avatar}}"
class="md-avatar avatar"
alt="{{user.name}}"/>
<!-- / USER AVATAR -->
<md-icon class="s-16 status" [ngClass]="user.status" [mdMenuTriggerFor]="userStatusMenu"></md-icon>
<!-- USER STATUS -->
<md-menu id="user-status-menu" #userStatusMenu="mdMenu">
<button md-menu-item (click)="setUserStatus('online')">
<div fxLayout="row" fxLayoutAlign="start center">
<md-icon class="s-16 status online"></md-icon>
<span>Online</span>
</div>
</button>
<button md-menu-item (click)="setUserStatus('away')">
<div fxLayout="row" fxLayoutAlign="start center">
<md-icon class="s-16 status away"></md-icon>
<span>Away</span>
</div>
</button>
<button md-menu-item (click)="setUserStatus('do-not-disturb')">
<div fxLayout="row" fxLayoutAlign="start center">
<md-icon class="s-16 status do-not-disturb"></md-icon>
<span>Do not disturb</span>
</div>
</button>
<button md-menu-item (click)="setUserStatus('offline')">
<div fxLayout="row" fxLayoutAlign="start center">
<md-icon class="s-16 status offline"></md-icon>
<span>Offline</span>
</div>
</button>
</md-menu>
<!-- / USER STATUS -->
</div>
<!-- / USER AVATAR -->
<div>
<button md-button class="mat-icon-button"
[mdMenuTriggerFor]="userMenu"
aria-label="more">
<md-icon>more_vert</md-icon>
</button>
<md-menu #userMenu="mdMenu">
<button md-menu-item (click)="changeLeftSidenavView('user')">
Profile
</button>
<button md-menu-item (click)="logout()">
Logout
</button>
</md-menu>
</div>
</div>
<!-- / TOOLBAR TOP -->
<!-- TOOLBAR BOTTOM -->
<md-toolbar-row>
<!-- SEARCH -->
<div class="search-wrapper" fxFlex fxLayout="row" fxLayoutAlign="start center">
<div class="search" fxFlex fxLayout="row" fxLayoutAlign="start center">
<md-icon>search</md-icon>
<input [(ngModel)]="chatSearch.name" type="text" placeholder="Search or start new chat" fxFlex>
</div>
</div>
<!-- / SEARCH -->
</md-toolbar-row>
<!-- / TOOLBAR BOTTOM -->
</md-toolbar>
<!-- / CHATS TOOLBAR -->
</div>
<!-- / SIDENAV HEADER -->
<!-- SIDENAV CONTENT -->
<div class="sidenav-content" perfect-scrollbar fxFlex>
<!-- CHATS CONTENT -->
<div>
<!-- CHATS LIST-->
<div class="chat-list" fxLayout="column">
<div md-subheader *ngIf="(user.chatList | filterBy: chatSearch).length > 0">
Chats
</div>
<button md-button class="contact"
*ngFor="let chat of user.chatList | filterBy: chatSearch"
(click)="getChat(chat.contactId)" ngClass="{'unread':contact.unread}">
<div fxLayout="row" fxLayoutAlign="start center">
<div class="avatar-wrapper" fxFlex="0 1 auto" fxLayoutAlign="center center">
<img [src]="contacts |getById:chat.contactId:'avatar'"
class="avatar"
alt="{{contacts |getById:chat.contactId:'name'}}"/>
<md-icon class="s-16 status" [ngClass]="contacts |getById:chat.contactId:'status'"></md-icon>
</div>
<div fxLayout="row" fxFlex>
<div class="" fxFlex fxLayout="column" fxLayoutAlign="center start">
<div class="contact-name">{{contacts |getById:chat.contactId:'name'}}</div>
<p class="contact-last-message text-truncate">{{chat.lastMessage}}</p>
</div>
<div fxLayout="column" fxLayoutAlign="center end">
<div class="contact-last-message-time">
{{chat.lastMessageTime | date}}
</div>
<div *ngIf="chat.unread" class="unread-message-count">{{chat.unread}}</div>
</div>
</div>
</div>
</button>
</div>
<!-- / CHATS LIST-->
<!-- CONTACTS LIST-->
<div class="contact-list" fxLayout="column">
<div md-subheader *ngIf="(contacts| filterBy: chatSearch).length > 0">
Contacts
</div>
<button md-button class="contact"
ng-show="chatSearch"
*ngFor="let contact of contacts| filterBy: chatSearch"
(click)="getChat(contact.id)">
<div fxLayout="row" fxLayoutAlign="start center">
<div class="avatar-wrapper" fxFlex="0 1 auto">
<img src="{{contact.avatar}}" class="md-avatar avatar" alt="{{contact.name}}"/>
<md-icon class="s-16 status" [ngClass]="contact.status"></md-icon>
</div>
<div class="" fxLayout="column" fxLayoutAlign="center start">
<div class="contact-name">{{contact.name}}</div>
<p class="contact-mood">{{contact.mood}}</p>
</div>
</div>
</button>
</div>
<!-- / CONTACTS LIST-->
<!-- NO RESULTS MESSAGE -->
<div *ngIf="(contacts| filterBy: chatSearch).length === 0" class="no-results-message">
No results..
</div>
<!-- NO RESULTS MESSAGE-->
</div>
<!-- / CHATS CONTENT -->
</div>
<!-- / SIDENAV CONTENT -->

View File

@ -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;
}
}
}

View File

@ -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');
}
}

View File

@ -0,0 +1,11 @@
<div [ngSwitch]="view" class="views">
<fuse-chat-chats-sidenav class="view"
*ngSwitchCase="'chats'"
[@slideInRight]>
</fuse-chat-chats-sidenav>
<fuse-chat-user-sidenav class="view"
*ngSwitchCase="'user'"
[@slideInLeft]>
</fuse-chat-user-sidenav>
</div>

View File

@ -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;
}
}
}

View File

@ -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;
});
}
}

View File

@ -0,0 +1,78 @@
<!-- SIDENAV HEADER -->
<div class="sidenav-header">
<!-- USER TOOLBAR -->
<md-toolbar>
<!-- TOOLBAR TOP -->
<div fxFlex fxLayout="row" fxLayoutAlign="space-between center">
<button md-button class="mat-icon-button" (click)="changeLeftSidenavView('chats')" aria-label="back">
<md-icon>arrow_back</md-icon>
</button>
</div>
<!-- / TOOLBAR TOP -->
<!-- TOOLBAR BOTTOM -->
<md-toolbar-row class="toolbar-bottom" fxLayout="column" fxLayoutAlign="center center">
<img [src]="user.avatar" class="avatar user-avatar huge" alt="{{user.name}}"/>
<div class="user-name my-8">{{user.name}}</div>
</md-toolbar-row>
<!-- / TOOLBAR BOTTOM -->
</md-toolbar>
<!-- / USER TOOLBAR -->
</div>
<!-- SIDENAV CONTENT -->
<div class="sidenav-content p-16" perfect-scrollbar fxFlex>
<!-- USER MOOD -->
<md-card>
<form [formGroup]="userForm" fxLayout="column">
<md-input-container class="mb-24" fxFlex>
<textarea mdInput placeholder="Mood" name="mood"
formControlName="mood" rows="3"></textarea>
</md-input-container>
<md-radio-group formControlName="status" fxLayout="column">
<md-radio-button value="online" class="py-8">
<div fxLayout="row" fxLayoutAlign="start center">
<md-icon class="status online mr-8"></md-icon>
<span class="mat-h4 m-0">Online</span>
</div>
</md-radio-button>
<md-radio-button value="away" class="py-8">
<div fxLayout="row" fxLayoutAlign="start center">
<md-icon class="status away mr-8"></md-icon>
<span class="mat-h4 m-0">Away</span>
</div>
</md-radio-button>
<md-radio-button value="do-not-disturb" class="py-8">
<div fxLayout="row" fxLayoutAlign="start center">
<md-icon class="status do-not-disturb mr-8"></md-icon>
<span class="mat-h4 m-0">Do not disturb</span>
</div>
</md-radio-button>
<md-radio-button value="offline" class="py-8">
<div fxLayout="row" fxLayoutAlign="start center">
<md-icon class="status offline mr-8"></md-icon>
<span class="mat-h4 m-0">Offline</span>
</div>
</md-radio-button>
</md-radio-group>
</form>
</md-card>
<!-- / USER MOOD -->
</div>

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -0,0 +1,47 @@
<!-- SIDENAV HEADER -->
<div class="sidenav-header" *ngIf="contact">
<!-- CONTACT TOOLBAR -->
<md-toolbar>
<!-- TOOLBAR TOP -->
<div fxFlex fxLayout="row" fxLayoutAlign="space-between center">
<div>Contact Info</div>
<button md-button class="mat-icon-button" fuseMdSidenavToggler="chat-right-sidenav" aria-label="close">
<md-icon>close</md-icon>
</button>
</div>
<!-- / TOOLBAR TOP -->
<!-- TOOLBAR BOTTOM -->
<md-toolbar-row class="toolbar-bottom" fxLayout="column" fxLayoutAlign="center center">
<img [src]="contact.avatar" class="avatar contact-avatar huge" alt="{{contact.name}}"/>
<div class="contact-name my-8">{{contact.name}}</div>
</md-toolbar-row>
<!-- / TOOLBAR BOTTOM -->
</md-toolbar>
<!-- / CONTACT TOOLBAR -->
</div>
<!-- SIDENAV CONTENT -->
<div class="sidenav-content p-16" perfect-scrollbar fxFlex *ngIf="contact">
<!-- CONTACT MOOD -->
<md-card>
<md-input-container fxFlex>
<textarea mdInput placeholder="Mood" name="mood"
[value]="contact.mood" rows="3" disabled>
</textarea>
</md-input-container>
</md-card>
<!-- / CONTACT MOOD -->
</div>

View File

@ -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;
}
}

View File

@ -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;
});
}
}

View File

@ -0,0 +1,8 @@
<div [ngSwitch]="view" class="views">
<fuse-chat-contact-sidenav class="view"
*ngSwitchCase="'contact'"
[@slideInRight]>
</fuse-chat-contact-sidenav>
</div>

View File

@ -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;
}
}
}

View File

@ -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;
});
}
}