(Todo App) Todo app created,

drag drop libarary added,
(Chat App) Chat app module name changed.
This commit is contained in:
mustafahlvc 2017-07-27 00:33:17 +03:00
parent 6237b1132a
commit 46ad46bb02
26 changed files with 2025 additions and 18 deletions

51
package-lock.json generated
View File

@ -224,6 +224,14 @@
"resolved": "https://registry.npmjs.org/@swimlane/ngx-datatable/-/ngx-datatable-9.3.1.tgz",
"integrity": "sha1-qbEwcycSHd+HTnW6a9amxpySZaE="
},
"@swimlane/ngx-dnd": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@swimlane/ngx-dnd/-/ngx-dnd-2.2.0.tgz",
"integrity": "sha512-ndkJZlNx68MtQ9kY4mKnwwGrlepaj0Wd/QsDn2cXUBjsOJPaX1f8EUZ98OXdg6cc1yQP8JxTkgmgRPDsiV6CSw==",
"requires": {
"dragula": "3.7.2"
}
},
"@types/jasmine": {
"version": "2.5.53",
"resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-2.5.53.tgz",
@ -600,6 +608,11 @@
"integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=",
"dev": true
},
"atoa": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/atoa/-/atoa-1.0.0.tgz",
"integrity": "sha1-DMDpGkgOc4+SPrwQNnZHF3mzSkk="
},
"autoprefixer": {
"version": "6.7.7",
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-6.7.7.tgz",
@ -1548,6 +1561,15 @@
"integrity": "sha1-t9ETrueo3Se9IRM8TcJSnfFyHu0=",
"dev": true
},
"contra": {
"version": "1.9.4",
"resolved": "https://registry.npmjs.org/contra/-/contra-1.9.4.tgz",
"integrity": "sha1-9TveQtfltZhcrk2ZqNYQUm3o8o0=",
"requires": {
"atoa": "1.0.0",
"ticky": "1.0.1"
}
},
"convert-source-map": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.5.0.tgz",
@ -1676,6 +1698,21 @@
"which": "1.2.14"
}
},
"crossvent": {
"version": "1.5.4",
"resolved": "https://registry.npmjs.org/crossvent/-/crossvent-1.5.4.tgz",
"integrity": "sha1-2ixPj0DJR4JRe/K+7BBEFIGUq5I=",
"requires": {
"custom-event": "1.0.0"
},
"dependencies": {
"custom-event": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.0.tgz",
"integrity": "sha1-LkYovhncSyFLXAJjDFlx6BFhgGI="
}
}
},
"cryptiles": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz",
@ -2109,6 +2146,15 @@
"domelementtype": "1.3.0"
}
},
"dragula": {
"version": "3.7.2",
"resolved": "https://registry.npmjs.org/dragula/-/dragula-3.7.2.tgz",
"integrity": "sha1-SjXJ05gf+sGpScKcpyhQWOhzk84=",
"requires": {
"contra": "1.9.4",
"crossvent": "1.5.4"
}
},
"ecc-jsbn": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz",
@ -7447,6 +7493,11 @@
"integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
"dev": true
},
"ticky": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/ticky/-/ticky-1.0.1.tgz",
"integrity": "sha1-t8+nHnaPHJAAxJe5FRswlHxQ5G0="
},
"timers-browserify": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.2.tgz",

View File

@ -25,6 +25,7 @@
"@angular/platform-browser-dynamic": "^4.3.1",
"@angular/router": "^4.3.1",
"@swimlane/ngx-datatable": "^9.3.1",
"@swimlane/ngx-dnd": "^2.2.0",
"angular-calendar": "^0.19.0",
"angular-in-memory-web-api": "^0.3.2",
"core-js": "^2.4.1",

View File

@ -5,7 +5,6 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { AppComponent } from './app.component';
import { RouterModule, Routes } from '@angular/router';
import 'hammerjs';
import { ChatModule } from './main/apps/chat/chat.module';
import { ProjectModule } from './main/apps/dashboards/project/project.module';
import { FuseLayoutService } from './core/services/layout.service';
import { FuseNavigationService } from './core/components/navigation/navigation.service';
@ -20,7 +19,6 @@ 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
@ -33,12 +31,16 @@ const appRoutes: Routes = [
},
{
path : 'apps/chat',
loadChildren: './main/apps/chat/chat.module#ChatModule'
loadChildren: './main/apps/chat/chat.module#FuseChatModule'
},
{
path : 'apps/calendar',
loadChildren: './main/apps/calendar/calendar.module#FuseCalendarModule'
},
{
path : 'apps/todo',
loadChildren: './main/apps/todo/todo.module#FuseTodoModule'
},
{
path : '**',
redirectTo: 'apps/dashboards/project'

View File

@ -15,6 +15,7 @@ import {
import { FusePipesModule } from '../pipes/pipes.module';
import { ColorPickerModule } from 'ngx-color-picker';
import { FuseConfirmDialogComponent } from '../components/confirm-dialog/confirm-dialog.component';
import { NgxDnDModule } from '@swimlane/ngx-dnd';
@NgModule({
declarations : [
@ -31,7 +32,8 @@ import { FuseConfirmDialogComponent } from '../components/confirm-dialog/confirm
FusePipesModule,
PerfectScrollbarModule,
ReactiveFormsModule,
ColorPickerModule
ColorPickerModule,
NgxDnDModule
],
exports : [
FlexLayoutModule,
@ -44,7 +46,8 @@ import { FuseConfirmDialogComponent } from '../components/confirm-dialog/confirm
FusePipesModule,
PerfectScrollbarModule,
ReactiveFormsModule,
ColorPickerModule
ColorPickerModule,
NgxDnDModule
],
entryComponents: [FuseConfirmDialogComponent]
})

View File

@ -2,16 +2,7 @@ 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';
import { TodoFakeDb } from './todo';
export class FuseFakeDbService implements InMemoryDbService
{
@ -25,7 +16,10 @@ export class FuseFakeDbService implements InMemoryDbService
'chat-contacts': ChatFakeDb.contacts,
'chat-chats' : ChatFakeDb.chats,
'chat-user' : ChatFakeDb.user,
'calendar' : CalendarFakeDb.data
'calendar' : CalendarFakeDb.data,
'todo-todos' : TodoFakeDb.todos,
'todo-filters' : TodoFakeDb.filters,
'todo-tags' : TodoFakeDb.tags
};
}

View File

@ -0,0 +1,324 @@
export class TodoFakeDb
{
public static todos = [
{
'id' : '561551bd7fe2ff461101c192',
'title' : 'Proident tempor est nulla irure ad est',
'notes' : 'Id nulla nulla proident deserunt deserunt proident in quis. Cillum reprehenderit labore id anim laborum.',
'startDate': 'Wednesday, January 29, 2014 3:17 PM',
'dueDate' : null,
'completed': false,
'starred' : false,
'important': false,
'deleted' : false,
'tags' : [1]
},
{
'id' : '561551bd4ac1e7eb77a3a750',
'title' : 'Magna quis irure quis ea pariatur laborum',
'notes' : '',
'startDate': 'Sunday, February 1, 2015 1:30 PM',
'dueDate' : 'Friday, December 30, 2016 10:07 AM',
'completed': false,
'starred' : false,
'important': true,
'deleted' : false,
'tags' : [1, 4]
},
{
'id' : '561551bd917bfec2ddef2d49',
'title' : 'Ullamco duis commodo sint ad aliqua aute',
'notes' : 'Sunt laborum enim nostrud ea fugiat cillum mollit aliqua exercitation ad elit.',
'startDate': 'Friday, April 11, 2014 3:43 AM',
'dueDate' : 'Wednesday, July 26, 2017 11:14 AM',
'completed': false,
'starred' : true,
'important': true,
'deleted' : false,
'tags' : [3]
},
{
'id' : '561551bdeeb2fd6877e18c29',
'title' : 'Eiusmod non occaecat pariatur Lorem in ex',
'notes' : 'Nostrud anim mollit incididunt qui qui sit commodo duis. Anim amet irure aliquip duis nostrud sit quis fugiat ullamco non dolor labore. Lorem sunt voluptate laboris culpa proident. Aute eiusmod aliqua exercitation irure exercitation qui laboris mollit occaecat eu occaecat fugiat.',
'startDate': 'Wednesday, May 7, 2014 4:14 AM',
'dueDate' : 'Friday, December 15, 2017 4:01 AM',
'completed': true,
'starred' : true,
'important': false,
'deleted' : false,
'tags' : [2]
},
{
'id' : '561551bdf38eae0134ae43d4',
'title' : 'Lorem magna cillum consequat consequat mollit',
'notes' : 'Velit ipsum proident ea incididunt et. Consectetur eiusmod laborum voluptate duis occaecat ullamco sint enim proident.',
'startDate': 'Sunday, August 23, 2015 11:19 PM',
'dueDate' : 'Friday, July 8, 2016 10:49 AM',
'completed': false,
'starred' : false,
'important': false,
'deleted' : false,
'tags' : [5, 4]
},
{
'id' : '561551bd32f1588c814a0ccd',
'title' : 'Quis irure cupidatat ad consequat reprehenderit excepteur',
'notes' : 'Esse nisi mollit aliquip mollit aute consequat adipisicing. Do excepteur dolore proident cupidatat pariatur irure consequat incididunt.',
'startDate': 'Sunday, June 7, 2015 10:49 AM',
'dueDate' : 'Monday, January 9, 2017 8:34 AM',
'completed': false,
'starred' : true,
'important': false,
'deleted' : false,
'tags' : [2, 3]
},
{
'id' : '561551bd0bb4b08ca77038ef',
'title' : 'Officia voluptate tempor ut mollit ea cillum',
'notes' : 'Deserunt veniam reprehenderit do elit magna ut.',
'startDate': 'Saturday, October 18, 2014 4:25 AM',
'dueDate' : 'Sunday, August 21, 2016 10:48 PM',
'completed': true,
'starred' : false,
'important': false,
'deleted' : false,
'tags' : [2, 4]
},
{
'id' : '561551bdf84eec913835ebe5',
'title' : 'Sit exercitation cupidatat minim est ipsum excepteur',
'notes' : '',
'startDate': 'Friday, August 8, 2014 5:45 AM',
'dueDate' : 'Wednesday, June 15, 2016 1:53 PM',
'completed': true,
'starred' : false,
'important': true,
'deleted' : false,
'tags' : [1, 3]
},
{
'id' : '561551bd2047cc709af0f670',
'title' : 'Sunt fugiat officia nisi minim sunt duis',
'notes' : 'Eiusmod eiusmod sint aliquip exercitation cillum. Magna nulla officia ex consectetur ea ad excepteur in qui.',
'startDate': 'Monday, July 13, 2015 1:55 PM',
'dueDate' : 'Thursday, March 3, 2016 2:26 PM',
'completed': false,
'starred' : false,
'important': false,
'deleted' : false,
'tags' : [
{
'id' : 5,
'name' : 'mobile',
'label': 'Mobile',
'color': '#9C27B0'
}
]
},
{
'id' : '561551bd73d1a627e97005ce',
'title' : 'Non cupidatat enim quis aliquip minim laborum',
'notes' : 'Qui cillum eiusmod nostrud sunt dolore velit nostrud labore voluptate ad dolore. Eu Lorem anim pariatur aliqua. Ullamco ut dolor velit esse occaecat dolore eu cillum commodo qui. Nulla dolor consequat voluptate magna ut commodo magna consectetur non aute proident.',
'startDate': 'Tuesday, November 11, 2014 6:36 PM',
'dueDate' : 'Tuesday, August 9, 2016 7:18 AM',
'completed': false,
'starred' : false,
'important': false,
'deleted' : false,
'tags' : [2]
},
{
'id' : '561551bd8f7d793ded0a2353',
'title' : 'Dolor ex occaecat magna labore laboris qui',
'notes' : 'Incididunt qui excepteur eiusmod elit cillum occaecat voluptate cillum nostrud. Dolor ullamco ullamco eiusmod do sunt adipisicing pariatur. In esse esse labore id reprehenderit sint do. Pariatur culpa dolor tempor qui excepteur duis do anim minim ipsum.',
'startDate': 'Monday, June 9, 2014 3:15 PM',
'dueDate' : 'Wednesday, October 19, 2016 3:38 PM',
'completed': false,
'starred' : false,
'important': true,
'deleted' : false,
'tags' : [3]
},
{
'id' : '561551bdaa586f72d0be02cc',
'title' : 'Ex nisi amet id dolore nostrud esse',
'notes' : '',
'startDate': 'Thursday, January 15, 2015 6:11 PM',
'dueDate' : 'Sunday, August 20, 2017 10:02 AM',
'completed': false,
'starred' : true,
'important': true,
'deleted' : false,
'tags' : [4]
},
{
'id' : '561551bd9f1c2de5b27f537b',
'title' : 'In dolor velit labore dolore ex eiusmod',
'notes' : '',
'startDate': 'Monday, March 10, 2014 12:50 AM',
'dueDate' : 'Thursday, January 26, 2017 3:10 PM',
'completed': false,
'starred' : false,
'important': false,
'deleted' : false,
'tags' : [4]
},
{
'id' : '561551bd26e21bb5e85b35cb',
'title' : 'Sunt voluptate aliquip exercitation minim magna sit',
'notes' : '',
'startDate': 'Tuesday, March 24, 2015 10:54 PM',
'dueDate' : 'Wednesday, August 23, 2017 5:35 PM',
'completed': false,
'starred' : false,
'important': false,
'deleted' : false,
'tags' : [4]
},
{
'id' : '561551bd719860cf0ad2011a',
'title' : 'Nisi et ullamco minim ea proident tempor',
'notes' : 'Dolor veniam dolor cillum Lorem magna nisi in occaecat nulla dolor ea eiusmod.',
'startDate': 'Friday, February 14, 2014 10:03 AM',
'dueDate' : 'Saturday, July 8, 2017 11:54 PM',
'completed': false,
'starred' : true,
'important': false,
'deleted' : false,
'tags' : [2, 4]
},
{
'id' : '561551bd49d800c243264a91',
'title' : 'Sit ipsum mollit cupidatat adipisicing officia aliquip',
'notes' : '',
'startDate': 'Wednesday, December 10, 2014 9:25 AM',
'dueDate' : 'Friday, March 25, 2016 12:29 AM',
'completed': true,
'starred' : false,
'important': false,
'deleted' : false,
'tags' : [1]
},
{
'id' : '561551bd061990eaf40fb64f',
'title' : 'Amet sunt et quis amet commodo quis',
'notes' : 'Nulla dolore consequat aliqua sint consequat elit qui occaecat et.',
'startDate': 'Saturday, March 1, 2014 3:59 PM',
'dueDate' : 'Saturday, November 7, 2015 2:00 PM',
'completed': false,
'starred' : false,
'important': true,
'deleted' : false,
'tags' : [1]
},
{
'id' : '561551be81d05fa94711e7f3',
'title' : 'Ut eiusmod ex ea eiusmod culpa incididunt',
'notes' : 'Fugiat non incididunt officia ex incididunt occaecat. Voluptate nostrud culpa aliquip mollit incididunt non dolore.',
'startDate': 'Monday, February 2, 2015 3:07 PM',
'dueDate' : 'Saturday, October 14, 2017 6:57 AM',
'completed': false,
'starred' : false,
'important': false,
'deleted' : false,
'tags' : [2]
},
{
'id' : '561551be05c093a80e0c8d05',
'title' : 'Proident reprehenderit laboris pariatur ut et nisi',
'notes' : 'Reprehenderit proident ut ad cillum quis velit quis aliqua ut aliquip tempor ullamco.',
'startDate': 'Sunday, June 14, 2015 4:40 AM',
'dueDate' : 'Wednesday, February 10, 2016 10:47 AM',
'completed': true,
'starred' : true,
'important': true,
'deleted' : false,
'tags' : [5]
},
{
'id' : '561551be3bb43a5bd431c2fc',
'title' : 'Aliqua aliquip aliquip aliquip et exercitation aute',
'notes' : 'Adipisicing Lorem tempor ex anim. Labore tempor laboris nostrud dolore voluptate ullamco. Fugiat ex deserunt anim minim esse velit laboris aute ea duis incididunt. Elit irure id Lorem incididunt laborum aliquip consectetur est irure sunt. Ut labore anim nisi aliqua tempor laborum nulla cillum. Duis irure consequat cillum magna cillum eiusmod ut. Et exercitation voluptate quis deserunt elit quis dolor deserunt ex ex esse ex.',
'startDate': 'Saturday, May 3, 2014 1:32 AM',
'dueDate' : 'Monday, September 12, 2016 9:16 PM',
'completed': true,
'starred' : false,
'important': true,
'deleted' : true,
'tags' : [3]
}
];
public static filters = [
{
'id' : 0,
'handle': 'starred',
'title' : 'Starred',
'icon' : 'star'
},
{
'id' : 1,
'handle': 'important',
'title' : 'Priority',
'icon' : 'error'
},
{
'id' : 2,
'handle': 'dueDate',
'title' : 'Sheduled',
'icon' : 'schedule'
},
{
'id' : 3,
'handle': 'today',
'title' : 'Today',
'icon' : 'today'
},
{
'id' : 4,
'handle': 'completed',
'title' : 'Done',
'icon' : 'check'
},
{
'id' : 4,
'handle': 'deleted',
'title' : 'Deleted',
'icon' : 'delete'
}
];
public static tags = [
{
'id' : 1,
'handle': 'frontend',
'title' : 'Frontend',
'color' : '#388E3C'
},
{
'id' : 2,
'handle': 'backend',
'title' : 'Backend',
'color' : '#F44336'
},
{
'id' : 3,
'handle': 'api',
'title' : 'API',
'color' : '#FF9800'
},
{
'id' : 4,
'handle': 'issue',
'title' : 'Issue',
'color' : '#0091EA'
},
{
'id' : 5,
'handle': 'mobile',
'title' : 'Mobile',
'color' : '#9C27B0'
}
];
}

View File

@ -39,6 +39,6 @@ const routes: Routes = [
ChatService
]
})
export class ChatModule
export class FuseChatModule
{
}

View File

@ -178,7 +178,7 @@ export class ChatService implements Resolve<any>
}
/**
* The Mail App Main Resolver
* The Chat App Main Resolver
* @param {ActivatedRouteSnapshot} route
* @param {RouterStateSnapshot} state
* @returns {Observable<any> | Promise<any> | any}

View File

@ -0,0 +1,55 @@
<!-- SIDENAV HEADER -->
<div class="header" fxLayout="column" fxLayoutAlign="space-between start">
<div class="logo" fxFlex fxLayout="row" fxLayoutAlign="start center">
<md-icon class="logo-icon">check_box</md-icon>
<span class="logo-text">To-do</span>
</div>
<div class="account" fxLayout="column">
<div class="title">John Doe</div>
<md-select class="account-selection" placeholder="Todo Selection"
floatPlaceholder="never"
[ngModel]="selectedAccount">
<md-option *ngFor="let account of (accounts | keys)" [value]="account.key">
{{account.value}}
</md-option>
</md-select>
</div>
</div>
<!-- / SIDENAV HEADER -->
<!-- SIDENAV CONTENT -->
<div class="content" perfect-scrollbar>
<div class="nav">
<div class="nav-item">
<a class="nav-link" md-ripple [routerLink]="'/apps/todo/all'" routerLinkActive="active">
<md-icon class="nav-link-icon">view_headline</md-icon>
<span>All</span>
</a>
</div>
<div class="nav-subheader">FILTERS</div>
<div class="nav-item" *ngFor="let filter of filters">
<a class="nav-link" md-ripple [routerLink]="'/apps/todo/filter/' + filter.handle" routerLinkActive="active">
<md-icon class="nav-link-icon" *ngIf="filter.icon">{{filter.icon}}</md-icon>
<span>{{filter.title}}</span>
</a>
</div>
<div class="nav-subheader">TAGS</div>
<div class="nav-item" *ngFor="let tag of tags">
<a class="nav-link" md-ripple [routerLink]="'/apps/todo/tag/' + tag.handle" routerLinkActive="active">
<md-icon class="nav-link-icon" [ngStyle]="{'color':tag.color}">label</md-icon>
<span>{{tag.title}}</span>
</a>
</div>
</div>
</div>
<!-- / SIDENAV CONTENT -->

View File

@ -0,0 +1,30 @@
:host {
display: flex;
flex: 1 0 auto;
flex-direction: column;
height: 100%;
.header {
.logo {
.logo-icon {
margin: 0 16px 0 0;
}
.logo-text {
font-size: 24px;
}
}
.account {
width: 100%;
.account-selection {
}
}
}
.content {
padding: 0 !important;
}
}

View File

@ -0,0 +1,54 @@
import { Component, OnDestroy, OnInit } from '@angular/core';
import { TodoService } from '../../todo.service';
import { Subscription } from 'rxjs/Subscription';
@Component({
selector : 'fuse-todo-main-sidenav',
templateUrl: './main-sidenav.component.html',
styleUrls : ['./main-sidenav.component.scss']
})
export class MainSidenavComponent implements OnInit, OnDestroy
{
folders: any[];
filters: any[];
tags: any[];
accounts: object;
selectedAccount: string;
// onFoldersChanged: Subscription;
onFiltersChanged: Subscription;
onTagsChanged: Subscription;
constructor(private todoService: TodoService)
{
// Data
this.accounts = {
'creapond' : 'johndoe@creapond.com',
'withinpixels': 'johndoe@withinpixels.com'
};
this.selectedAccount = 'creapond';
}
ngOnInit()
{
this.onFiltersChanged =
this.todoService.onFiltersChanged
.subscribe(filters => {
this.filters = filters;
});
this.onTagsChanged =
this.todoService.onTagsChanged
.subscribe(tags => {
this.tags = tags;
});
}
ngOnDestroy()
{
// this.onFoldersChanged.unsubscribe();
this.onFiltersChanged.unsubscribe();
// this.onTagsChanged.unsubscribe();
}
}

View File

@ -0,0 +1,102 @@
<div *ngIf="!todo" fxLayout="column" fxLayoutAlign="center center" fxFlex>
<md-icon class="s-120 mb-12">check_box</md-icon>
<span class="hint-text mat-h1">Select a todo</span>
</div>
<div *ngIf="todo">
<div class="todo-header" fxLayout="row" fxLayoutAlign="space-between center">
<button md-button class="mat-icon-button toggle-complete-button" (click)="toggleCompleted($event)"
aria-label="Toggle completed" fxFlex="0 1 auto">
<md-icon *ngIf="todo.completed">check_box</md-icon>
<md-icon *ngIf="!todo.completed">check_box_outline_blank</md-icon>
</button>
<div class="actions" fxLayout="row" fxLayoutAlign="start center">
<button md-button class="mat-icon-button" (click)="toggleDeleted($event)" aria-label="Toggle delete">
<md-icon *ngIf="todo.deleted">delete_forever</md-icon>
<md-icon *ngIf="!todo.deleted">delete</md-icon>
</button>
<button md-button class="mat-icon-button" (click)="toggleImportant($event)" aria-label="Toggle important">
<md-icon *ngIf="todo.important">error</md-icon>
<md-icon *ngIf="!todo.important">error_outline</md-icon>
</button>
<button md-button class="mat-icon-button" (click)="toggleStar($event)" aria-label="Toggle star">
<md-icon *ngIf="todo.starred">star</md-icon>
<md-icon *ngIf="!todo.starred">star_outline</md-icon>
</button>
<button md-icon-button [mdMenuTriggerFor]="labelMenu" fxFlex="0 1 auto">
<md-icon>label</md-icon>
</button>
<md-menu #labelMenu="mdMenu">
<button md-menu-item *ngFor="let tag of tags"
(click)="toggleTagOnTodo(tag.id)">
{{tag.title}}
</button>
</md-menu>
</div>
</div>
<div class="todo-content">
<form [formGroup]="todoForm">
<md-input-container class="title mt-8" floatPlaceholder="never" fxFill>
<textarea mdInput
name="title"
formControlName="title"
placeholder="Title"
mdTextareaAutosize>
</textarea>
</md-input-container>
<div class="tags mb-24" fxFlexFill fxLayout="row" fxLayoutWrap>
<div class="tag" *ngFor="let tagId of todo.tags"
[ngStyle]="{background: tags | getById:tagId:'color'}">{{tags | getById:tagId:'title'}}
</div>
</div>
<div fxFlexFill fxLayout="row">
<md-input-container fxFlex class="mr-16">
<input mdInput
name="start"
formControlName="startDate"
[mdDatepicker]="startDatePicker"
placeholder="Start Date">
<button mdSuffix [mdDatepickerToggle]="startDatePicker"></button>
</md-input-container>
<md-datepicker #startDatePicker></md-datepicker>
<md-input-container fxFlex>
<input mdInput
name="dueDate"
formControlName="dueDate"
[mdDatepicker]="dueDatePicker"
placeholder="Due Date">
<button mdSuffix [mdDatepickerToggle]="dueDatePicker"></button>
</md-input-container>
<md-datepicker #dueDatePicker></md-datepicker>
</div>
<md-input-container class="" fxFill>
<textarea mdInput
name="notes"
formControlName="notes"
placeholder="Notes"
md-maxlength="500"
mdTextareaAutosize
mdAutosizeMinRows="6">
</textarea>
</md-input-container>
</form>
</div>
</div>

View File

@ -0,0 +1,103 @@
@import '../../../../core/scss/fuse';
:host {
display: flex;
flex-direction: column;
flex: 1;
background: #FFFFFF;
padding: 24px;
.todo-header {
border-bottom: 1px solid rgba(0, 0, 0, 0.12);
.actions {
min-width: 88px;
}
}
.todo-content {
.title {
font-size: 17px;
font-weight: 500;
}
.tag {
font-size: 11px;
padding: 0 5px;
margin: 8px 6px 0 0;
}
.to {
color: rgba(0, 0, 0, 0.54);
.to-text {
margin-right: 4px;
text-transform: lowercase;
}
}
.info {
padding-bottom: 16px;
.avatar {
margin-right: 16px;
background-color: mat-color($accent);
}
.name {
margin-right: 8px;
font-weight: 500;
}
.toggle-details {
user-select: none;
text-decoration: underline;
padding-top: 16px;
cursor: pointer;
font-weight: 500;
}
.details {
padding-top: 8px;
.title {
font-weight: 500;
margin-right: 6px;
}
.detail {
color: rgba(0, 0, 0, 0.54);
}
}
}
}
.todo-attachments {
padding: 24px 0;
border-top: 1px solid rgba(0, 0, 0, 0.12);
.title {
margin-bottom: 16px;
font-weight: 500;
}
.attachment {
.preview {
width: 100px;
margin: 0 16px 8px 0;
}
.link {
margin-bottom: 2px;
}
.size {
font-size: 11px;
}
}
}
}

View File

@ -0,0 +1,128 @@
import { Component, OnDestroy, OnInit } from '@angular/core';
import { TodoService } from '../todo.service';
import { Todo } from '../todo.model';
import { Subscription } from 'rxjs/Subscription';
import { FormBuilder, FormControl, FormGroup } from '@angular/forms';
@Component({
selector : 'fuse-todo-details',
templateUrl: './todo-details.component.html',
styleUrls : ['./todo-details.component.scss']
})
export class TodoDetailsComponent implements OnInit, OnDestroy
{
todo: Todo;
tags: any[];
todoForm: FormGroup;
onFormChange: any;
onCurrentTodoChanged: Subscription;
onTagsChanged: Subscription;
constructor(private todoService: TodoService,
private formBuilder: FormBuilder)
{
}
ngOnInit()
{
// Subscribe to update the current todo
this.onCurrentTodoChanged =
this.todoService.onCurrentTodoChanged
.subscribe(currentTodo => {
this.todo = currentTodo;
if ( this.todo )
{
this.todoForm = this.createTodoForm();
this.onFormChange = this.todoForm.valueChanges
.debounceTime(500)
.distinctUntilChanged()
.subscribe(data => {
this.todoService.updateTodo(data);
});
}
});
// Subscribe to update on tag change
this.onTagsChanged =
this.todoService.onTagsChanged
.subscribe(labels => {
this.tags = labels;
});
}
createTodoForm()
{
return this.formBuilder.group({
'id' : [this.todo.id],
'title' : [this.todo.title],
'notes' : [this.todo.notes],
'startDate': [this.todo.startDate],
'dueDate' : [this.todo.dueDate],
'completed': [this.todo.completed],
'starred' : [this.todo.starred],
'important': [this.todo.important],
'deleted' : [this.todo.deleted],
'tags' : [this.todo.tags]
});
}
toggleStar(event)
{
event.stopPropagation();
this.todo.toggleStar();
this.todoService.updateTodo(this.todo);
}
toggleImportant(event)
{
event.stopPropagation();
this.todo.toggleImportant();
this.todoService.updateTodo(this.todo);
}
/**
* Toggle Completed
* @param event
*/
toggleCompleted(event)
{
event.stopPropagation();
this.todo.toggleCompleted();
this.todoService.updateTodo(this.todo);
}
/**
* Toggle Deleted
* @param event
*/
toggleDeleted(event)
{
event.stopPropagation();
this.todo.toggleDeleted();
this.todoService.updateTodo(this.todo);
}
toggleTagOnTodo(tagId)
{
this.todoService.toggleTagOnTodo(tagId, this.todo);
}
ngOnDestroy()
{
this.onFormChange.unsubscribe();
this.onCurrentTodoChanged.unsubscribe();
}
}

View File

@ -0,0 +1,93 @@
<div fxLayout="row" fxLayoutAlign="start center">
<md-icon class="handle mr-16" ngxDragHandle (click)="$event.stopPropagation()" fxFlex="0 1 auto">drag_handle
</md-icon>
<md-checkbox [(ngModel)]="selected" (ngModelChange)="onSelectedChange()"
(click)="$event.stopPropagation()"
class="mr-16"
fxFlex="0 1 auto">
</md-checkbox>
<div fxLayout="row" fxLayoutAlign="start center" fxFlex>
<div class="info" fxFlex FlexLayout="column">
<div class="title">
{{todo.title}}
</div>
<div class="notes">
{{todo.notes}}
</div>
<div class="tags" fxLayout="row" fxLayoutAlign="start center" fxLayoutWrap>
<div class="tag" fxLayout="row" fxLayoutAlign="start center" *ngFor="let tagId of todo.tags">
<div class="tag-color" [ngStyle]="{'background-color': tags | getById:tagId:'color'}"></div>
<div class="tag-label">{{tags | getById:tagId:'title'}}</div>
</div>
</div>
</div>
<div class="actions" fxLayout="row" fxLayoutAlign="start center">
<button md-button class="mat-icon-button" (click)="toggleImportant($event)" aria-label="Toggle important">
<md-icon *ngIf="todo.important">error</md-icon>
<md-icon *ngIf="!todo.important">error_outline</md-icon>
</button>
<button md-button class="mat-icon-button" (click)="toggleStar($event)" aria-label="Toggle star">
<md-icon *ngIf="todo.starred">star</md-icon>
<md-icon *ngIf="!todo.starred">star_outline</md-icon>
</button>
<button md-button [mdMenuTriggerFor]="moreMenu" aria-label="More" class="mat-icon-button"
ng-click="$mdOpenMenu($event)">
<md-icon>more_vert</md-icon>
</button>
<md-menu #moreMenu="mdMenu">
<button md-menu-item aria-label="toggle done" (click)="toggleCompleted($event)">
<ng-container *ngIf="todo.completed">
<md-icon>check_box</md-icon>
<span>Mark as undone</span>
</ng-container>
<ng-container *ngIf="!todo.completed">
<md-icon>check_box_outline_blank</md-icon>
<span>Mark as done</span>
</ng-container>
</button>
<button md-menu-item aria-label="toggle important" (click)="toggleImportant($event)">
<ng-container *ngIf="todo.important">
<md-icon>error</md-icon>
<span>Remove important</span>
</ng-container>
<ng-container *ngIf="!todo.important">
<md-icon>error_outline</md-icon>
<span>Mark as important</span>
</ng-container>
</button>
<button md-menu-item aria-label="toggle star" (click)="toggleStar($event)">
<ng-container *ngIf="todo.starred">
<md-icon>star</md-icon>
<span>Remove star</span>
</ng-container>
<ng-container *ngIf="!todo.starred">
<md-icon>star_outline</md-icon>
<span>Add star</span>
</ng-container>
</button>
</md-menu>
</div>
</div>
</div>

View File

@ -0,0 +1,119 @@
@import 'src/app/core/scss/fuse';
.todo-list-item {
display: block;
position: relative;
padding: 16px 16px 16px 24px;
border-bottom: 1px solid rgba(0, 0, 0, 0.08);
text-transform: none;
cursor: pointer;
flex-shrink: 0;
background: #FFFFFF;
&.todo-item-placeholder {
background: rgba(0, 0, 0, 0.12);
* {
opacity: 0;
}
}
.handle {
height: 48px;
line-height: 48px;
cursor: move;
user-select: none;
}
.tags {
font-size: 12px;
.tag {
border-radius: 2px;
margin: 8px 4px 0 0;
padding: 3px 8px;
background-color: rgba(0, 0, 0, 0.08);
.tag-color {
width: 8px;
height: 8px;
margin-right: 8px;
border-radius: 50%;
}
}
}
&.completed {
background: #EEEEEE;
.title,
.notes {
color: rgba(0, 0, 0, 0.54);
text-decoration: line-through;
}
}
&.selected {
background: #FFF8E1;
}
.info {
margin: 0 16px 0 8px;
.title {
font-size: 15px;
font-weight: 500;
}
.notes {
margin-top: 4px;
}
}
.buttons {
.is-starred {
margin: 0 0 0 16px;
}
.is-important {
margin: 0;
}
}
&:not(.has-handle):not(.move-disabled),
&.has-handle [ngxdraghandle],
&.has-handle [ngxDragHandle] {
cursor: move;
}
.ngx-dnd-content {
user-select: none;
}
&.gu-mirror {
position: fixed !important;
margin: 0 !important;
z-index: 9999 !important;
opacity: 0.8;
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=80)";
filter: alpha(opacity=80);
@include mat-elevation(7);
}
&.gu-hide {
display: none !important;
}
&.gu-unselectable {
-webkit-user-select: none !important;
-moz-user-select: none !important;
-ms-user-select: none !important;
user-select: none !important;
}
&.gu-transit {
opacity: 0.2;
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=20)";
filter: alpha(opacity=20);
}
}

View File

@ -0,0 +1,107 @@
import { Component, HostBinding, Input, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core';
import { Todo } from '../../todo.model';
import { TodoService } from '../../todo.service';
import { Subscription } from 'rxjs/Subscription';
@Component({
selector : 'fuse-todo-list-item',
templateUrl : './todo-list-item.component.html',
styleUrls : ['./todo-list-item.component.scss'],
encapsulation: ViewEncapsulation.None
})
export class TodoListItemComponent implements OnInit, OnDestroy
{
@Input() todo: Todo;
tags: any[];
@HostBinding('class.selected') selected: boolean;
@HostBinding('class.completed') completed: boolean;
onSelectedTodosChanged: Subscription;
onTagsChanged: Subscription;
constructor(private todoService: TodoService)
{
}
ngOnInit()
{
// Set the initial values
this.todo = new Todo(this.todo);
this.completed = this.todo.completed;
// Subscribe to update on selected todo change
this.onSelectedTodosChanged =
this.todoService.onSelectedTodosChanged
.subscribe(selectedTodos => {
this.selected = false;
if ( selectedTodos.length > 0 )
{
for ( const todo of selectedTodos )
{
if ( todo.id === this.todo.id )
{
this.selected = true;
break;
}
}
}
});
// Subscribe to update on tag change
this.onTagsChanged =
this.todoService.onTagsChanged
.subscribe(tags => {
this.tags = tags;
});
}
ngOnDestroy()
{
this.onSelectedTodosChanged.unsubscribe();
}
onSelectedChange()
{
this.todoService.toggleSelectedTodo(this.todo.id);
}
/**
* Toggle star
* @param event
*/
toggleStar(event)
{
event.stopPropagation();
this.todo.toggleStar();
this.todoService.updateTodo(this.todo);
}
/**
* Toggle Important
* @param event
*/
toggleImportant(event)
{
event.stopPropagation();
this.todo.toggleImportant();
this.todoService.updateTodo(this.todo);
}
/**
* Toggle Completed
* @param event
*/
toggleCompleted(event)
{
event.stopPropagation();
this.todo.toggleCompleted();
this.todoService.updateTodo(this.todo);
}
}

View File

@ -0,0 +1,13 @@
<div *ngIf="todos.length === 0" fxLayout="column" fxLayoutAlign="center center" fxFlex>
<span class="hint-text mat-h3">There are no todos!</span>
</div>
<div class="todo-list" ngxDroppable [model]="todos" (out)="log($event)">
<fuse-todo-list-item class="todo-list-item has-handle"
*ngFor="let todo of todos" [todo]="todo"
ngxDraggable
[model]="todo"
(click)="readTodo(todo.id)"
[ngClass]="{'current-todo':todo?.id == currentTodo?.id}"
md-ripple>
</fuse-todo-list-item>
</div>

View File

@ -0,0 +1,12 @@
:host {
flex-direction: column;
background: #FAFAFA;
position: relative;
padding: 0;
border-right: 1px solid rgba(0, 0, 0, .12);
}
.todo-list {
}

View File

@ -0,0 +1,102 @@
import { Component, OnDestroy, OnInit } from '@angular/core';
import { Todo } from '../todo.model';
import { ActivatedRoute } from '@angular/router';
import { TodoService } from '../todo.service';
import { Location } from '@angular/common';
import { Subscription } from 'rxjs/Subscription';
@Component({
selector : 'fuse-todo-list',
templateUrl: './todo-list.component.html',
styleUrls : ['./todo-list.component.scss']
})
export class TodoListComponent implements OnInit, OnDestroy
{
todos: Todo[];
currentTodo: Todo;
onTodosChanged: Subscription;
onCurrentTodoChanged: Subscription;
constructor(private route: ActivatedRoute,
private todoService: TodoService,
private location: Location)
{
}
ngOnInit()
{
// Subscribe to update todos on changes
this.onTodosChanged =
this.todoService.onTodosChanged
.subscribe(todos => {
this.todos = todos;
});
// Subscribe to update current todo on changes
this.onCurrentTodoChanged =
this.todoService.onCurrentTodoChanged
.subscribe(currentTodo => {
if ( !currentTodo )
{
// Set the current todo id to null to deselect the current todo
this.currentTodo = null;
// Handle the location changes
const tagId = this.route.snapshot.params.tagId,
filterHandle = this.route.snapshot.params.filterHandle;
if ( tagId )
{
this.location.go('apps/todo/tag/' + tagId);
}
else if ( filterHandle )
{
this.location.go('apps/todo/filter/' + filterHandle);
}
}
else
{
this.currentTodo = currentTodo;
}
});
}
ngOnDestroy()
{
this.onTodosChanged.unsubscribe();
this.onCurrentTodoChanged.unsubscribe();
}
/**
* Read todo
* @param todoId
*/
readTodo(todoId)
{
const tagId = this.route.snapshot.params.tagId,
filterHandle = this.route.snapshot.params.filterHandle;
if ( tagId )
{
this.location.go('apps/todo/tag/' + tagId + '/' + todoId);
}
else if ( filterHandle )
{
this.location.go('apps/todo/filter/' + filterHandle + '/' + todoId);
}
else
{
this.location.go('apps/todo/all/' + todoId);
}
// Set current todo
this.todoService.setCurrentTodo(todoId);
}
log(ev)
{
console.info(this.todos);
console.info(ev);
}
}

View File

@ -0,0 +1,98 @@
<div id="todo" class="page-layout carded left-sidenav">
<!-- TOP BACKGROUND -->
<div class="top-bg"></div>
<!-- / TOP BACKGROUND -->
<md-sidenav-container>
<!-- SIDENAV -->
<md-sidenav class="sidenav mat-sidenav-opened" align="start" opened="true" mode="side"
fuseMdSidenavHelper="carded-left-sidenav" md-is-locked-open="gt-md">
<fuse-todo-main-sidenav></fuse-todo-main-sidenav>
</md-sidenav>
<!-- / SIDENAV -->
<!-- CENTER -->
<div class="center">
<!-- CONTENT HEADER -->
<div class="header" fxLayout="row" fxLayoutAlign="start center">
<div class="search-wrapper" fxFlex fxLayout="row" fxLayoutAlign="start center">
<button md-button class="mat-icon-button sidenav-toggle"
fuseMdSidenavToggler="carded-left-sidenav"
fxHide.gt-md aria-label="Toggle Sidenav">
<md-icon>menu</md-icon>
</button>
<div class="search" flex fxLayout="row" fxLayoutAlign="start center">
<md-icon>search</md-icon>
<input fxFlex type="text" placeholder="Search for an todo">
</div>
</div>
</div>
<!-- / CONTENT HEADER -->
<!-- CONTENT CARD -->
<div class="content-card">
<!-- CONTENT TOOLBAR -->
<div class="toolbar">
<md-checkbox (click)="toggleSelectAll()" [checked]="hasSelectedTodos"
[indeterminate]="isIndeterminate"></md-checkbox>
<button md-icon-button [mdMenuTriggerFor]="selectMenu">
<md-icon>arrow_drop_down</md-icon>
</button>
<md-menu #selectMenu="mdMenu">
<button md-menu-item (click)="selectTodos()">All</button>
<button md-menu-item (click)="deselectTodos()">None</button>
<button md-menu-item (click)="selectTodos('read', true)">Read</button>
<button md-menu-item (click)="selectTodos('read', false)">Unread</button>
<button md-menu-item (click)="selectTodos('starred', true)">Starred</button>
<button md-menu-item (click)="selectTodos('starred', false)">Unstarred</button>
<button md-menu-item (click)="selectTodos('important', true)">Important</button>
<button md-menu-item (click)="selectTodos('important', false)">Unimportant</button>
</md-menu>
<div class="toolbar-separator" *ngIf="hasSelectedTodos"></div>
<button md-icon-button [mdMenuTriggerFor]="labelMenu" *ngIf="hasSelectedTodos">
<md-icon>label</md-icon>
</button>
<md-menu #labelMenu="mdMenu">
<button md-menu-item *ngFor="let tag of tags" (click)="toggleTagOnSelectedTodos(tag.id)">
{{tag.title}}
</button>
</md-menu>
</div>
<!-- / CONTENT TOOLBAR -->
<!-- CONTENT -->
<div class="content">
<div fxLayout="row" fxFill>
<fuse-todo-list perfect-scrollbar fxFlex></fuse-todo-list>
<fuse-todo-details fxFlex perfect-scrollbar></fuse-todo-details>
</div>
</div>
<!-- / CONTENT -->
</div>
<!-- / CONTENT CARD -->
</div>
<!-- / CENTER -->
</md-sidenav-container>
</div>

View File

@ -0,0 +1,53 @@
@import "src/app/core/scss/fuse";
:host {
width: 100%;
height: 100%;
.center {
.header {
padding: 0 !important;
.search-wrapper {
@include mat-elevation(7);
.sidenav-toggle {
margin: 0;
width: 56px;
height: 56px;
background: #FFF;
border-radius: 0;
border-right: 1px solid rgba(0, 0, 0, .12);
}
.search {
width: 100%;
height: 56px;
line-height: 56px;
padding: 18px;
background: #FFFFFF;
input {
height: 56px;
padding-left: 16px;
color: rgba(0, 0, 0, 0.54);
border: none;
outline: none;
}
}
}
}
.content-card {
.toolbar {
}
.content {
padding: 0 !important;
}
}
}
}

View File

@ -0,0 +1,77 @@
import { Component, OnDestroy, OnInit } from '@angular/core';
import { Subscription } from 'rxjs/Subscription';
import { TodoService } from './todo.service';
@Component({
selector : 'fuse-todo',
templateUrl: './todo.component.html',
styleUrls : ['./todo.component.scss']
})
export class TodoComponent implements OnInit, OnDestroy
{
hasSelectedTodos: boolean;
isIndeterminate: boolean;
filters: any[];
tags: any[];
onSelectedTodosChanged: Subscription;
onFiltersChanged: Subscription;
onTagsChanged: Subscription;
constructor(private todoService: TodoService)
{
}
ngOnInit()
{
this.onSelectedTodosChanged =
this.todoService.onSelectedTodosChanged
.subscribe(selectedTodos => {
setTimeout(() => {
this.hasSelectedTodos = selectedTodos.length > 0;
this.isIndeterminate = (selectedTodos.length !== this.todoService.todos.length && selectedTodos.length > 0);
}, 0);
});
this.onFiltersChanged =
this.todoService.onFiltersChanged
.subscribe(folders => {
this.filters = this.todoService.filters;
});
this.onTagsChanged =
this.todoService.onTagsChanged
.subscribe(tags => {
this.tags = this.todoService.tags;
});
}
ngOnDestroy()
{
this.onSelectedTodosChanged.unsubscribe();
this.onFiltersChanged.unsubscribe();
this.onTagsChanged.unsubscribe();
}
toggleSelectAll()
{
this.todoService.toggleSelectAll();
}
selectTodos(filterParameter?, filterValue?)
{
this.todoService.selectTodos(filterParameter, filterValue);
}
deselectTodos()
{
this.todoService.deselectTodos();
}
toggleTagOnSelectedTodos(tagId)
{
this.todoService.toggleTagOnSelectedTodos(tagId);
}
}

View File

@ -0,0 +1,56 @@
export class Todo
{
id: string;
title: string;
notes: string;
startDate: string;
dueDate: boolean;
completed: boolean;
starred: boolean;
important: boolean;
deleted: boolean;
tags: [
{
'id': number,
'name': string,
'label': string,
'color': string
}
];
constructor(todo)
{
{
this.id = todo.id;
this.title = todo.title;
this.notes = todo.notes;
this.startDate = todo.startDate;
this.dueDate = todo.dueDate;
this.completed = todo.completed;
this.starred = todo.starred;
this.important = todo.important;
this.deleted = todo.deleted;
this.tags = todo.tags;
}
}
toggleStar()
{
this.starred = !this.starred;
}
toggleImportant()
{
this.important = !this.important;
}
toggleCompleted()
{
this.completed = !this.completed;
}
toggleDeleted()
{
this.deleted = !this.deleted;
}
}

View File

@ -0,0 +1,78 @@
import { NgModule } from '@angular/core';
import { SharedModule } from '../../../core/modules/shared.module';
import { RouterModule, Routes } from '@angular/router';
import { TodoComponent } from './todo.component';
import { TodoService } from './todo.service';
import { MainSidenavComponent } from './sidenavs/main/main-sidenav.component';
import { TodoListItemComponent } from './todo-list/todo-list-item/todo-list-item.component';
import { TodoListComponent } from './todo-list/todo-list.component';
import { TodoDetailsComponent } from './todo-details/todo-details.component';
const routes: Routes = [
{
path : 'all',
component: TodoComponent,
resolve : {
todo: TodoService
}
},
{
path : 'all/:todoId',
component: TodoComponent,
resolve : {
todo: TodoService
}
},
{
path : 'tag/:tagHandle',
component: TodoComponent,
resolve : {
todo: TodoService
}
},
{
path : 'tag/:tagHandle/:todoId',
component: TodoComponent,
resolve : {
todo: TodoService
}
},
{
path : 'filter/:filterHandle',
component: TodoComponent,
resolve : {
todo: TodoService
}
},
{
path : 'filter/:filterHandle/:todoId',
component: TodoComponent,
resolve : {
todo: TodoService
}
},
{
path : '**',
redirectTo: 'all'
}
];
@NgModule({
declarations: [
TodoComponent,
MainSidenavComponent,
TodoListItemComponent,
TodoListComponent,
TodoDetailsComponent
],
imports : [
SharedModule,
RouterModule.forChild(routes)
],
providers : [
TodoService
]
})
export class FuseTodoModule
{
}

View File

@ -0,0 +1,352 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs/Observable';
import { Http } from '@angular/http';
import { Todo } from './todo.model';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
@Injectable()
export class TodoService implements Resolve<any>
{
todos: Todo[];
selectedTodos: Todo[];
currentTodo: Todo;
filters: any[];
tags: any[];
routeParams: any;
onTodosChanged: BehaviorSubject<any> = new BehaviorSubject([]);
onSelectedTodosChanged: BehaviorSubject<any> = new BehaviorSubject([]);
onCurrentTodoChanged: BehaviorSubject<any> = new BehaviorSubject([]);
onFiltersChanged: BehaviorSubject<any> = new BehaviorSubject([]);
onTagsChanged: BehaviorSubject<any> = new BehaviorSubject([]);
constructor(private http: Http)
{
this.selectedTodos = [];
}
/**
* Resolve
* @param {ActivatedRouteSnapshot} route
* @param {RouterStateSnapshot} state
* @returns {Observable<any> | Promise<any> | any}
*/
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<any> | Promise<any> | any
{
this.routeParams = route.params;
return new Promise((resolve, reject) => {
Promise.all([
this.getFilters(),
this.getTags(),
this.getTodos()
]).then(
() => {
if ( this.routeParams.todoId )
{
this.setCurrentTodo(this.routeParams.todoId);
}
else
{
this.setCurrentTodo(null);
}
resolve();
},
reject
);
});
}
/**
* Get all filters
* @returns {Promise<any>}
*/
getFilters(): Promise<any>
{
return new Promise((resolve, reject) => {
this.http.get('api/todo-filters')
.subscribe(response => {
this.filters = response.json().data;
this.onFiltersChanged.next(this.filters);
resolve(this.filters);
}, reject);
});
}
/**
* Get all tags
* @returns {Promise<any>}
*/
getTags(): Promise<any>
{
return new Promise((resolve, reject) => {
this.http.get('api/todo-tags')
.subscribe(response => {
this.tags = response.json().data;
this.onTagsChanged.next(this.tags);
resolve(this.tags);
}, reject);
});
}
/**
* Get todos
* @returns {Promise<Todo[]>}
*/
getTodos(): Promise<Todo[]>
{
if ( this.routeParams.tagHandle )
{
return this.getTodosByTag(this.routeParams.tagHandle);
}
if ( this.routeParams.filterHandle )
{
return this.getTodosByFilter(this.routeParams.filterHandle);
}
return this.getTodosByParams(this.routeParams);
}
/**
* Get todos by params
* @param handle
* @returns {Promise<Todo[]>}
*/
getTodosByParams(handle): Promise<Todo[]>
{
return new Promise((resolve, reject) => {
this.http.get('api/todo-todos')
.subscribe(todos => {
this.todos = todos.json().data.map(todo => {
return new Todo(todo);
});
this.onTodosChanged.next(this.todos);
resolve(this.todos);
});
});
}
/**
* Get todos by filter
* @param handle
* @returns {Promise<Todo[]>}
*/
getTodosByFilter(handle): Promise<Todo[]>
{
let param = handle + '=true';
if ( handle === 'dueDate' )
{
param = handle + '=^$|\\s+';
}
return new Promise((resolve, reject) => {
this.http.get('api/todo-todos?' + param)
.subscribe(todos => {
this.todos = todos.json().data.map(todo => {
return new Todo(todo);
});
this.onTodosChanged.next(this.todos);
resolve(this.todos);
}, reject);
});
}
/**
* Get todos by tag
* @param handle
* @returns {Promise<Todo[]>}
*/
getTodosByTag(handle): Promise<Todo[]>
{
return new Promise((resolve, reject) => {
this.http.get('api/todo-tags?handle=' + handle)
.subscribe(tags => {
const tagId = tags.json().data[0].id;
this.http.get('api/todo-todos?tags=' + tagId)
.subscribe(todos => {
this.todos = todos.json().data.map(todo => {
return new Todo(todo);
});
this.onTodosChanged.next(this.todos);
resolve(this.todos);
}, reject);
});
});
}
/**
* Toggle selected todo by id
* @param id
*/
toggleSelectedTodo(id)
{
// First, check if we already have that todo as selected...
if ( this.selectedTodos.length > 0 )
{
for ( const todo of this.selectedTodos )
{
// ...delete the selected todo
if ( todo.id === id )
{
const index = this.selectedTodos.indexOf(todo);
if ( index !== -1 )
{
this.selectedTodos.splice(index, 1);
// Trigger the next event
this.onSelectedTodosChanged.next(this.selectedTodos);
// Return
return;
}
}
}
}
// If we don't have it, push as selected
this.selectedTodos.push(
this.todos.find(todo => {
return todo.id === id;
})
);
// Trigger the next event
this.onSelectedTodosChanged.next(this.selectedTodos);
}
/**
* Toggle select all
*/
toggleSelectAll()
{
if ( this.selectedTodos.length > 0 )
{
this.deselectTodos();
}
else
{
this.selectTodos();
}
}
selectTodos(filterParameter?, filterValue?)
{
this.selectedTodos = [];
// If there is no filter, select all todos
if ( filterParameter === undefined || filterValue === undefined )
{
this.selectedTodos = this.todos;
}
else
{
this.selectedTodos.push(...
this.todos.filter(todo => {
return todo[filterParameter] === filterValue;
})
);
}
// Trigger the next event
this.onSelectedTodosChanged.next(this.selectedTodos);
}
deselectTodos()
{
this.selectedTodos = [];
// Trigger the next event
this.onSelectedTodosChanged.next(this.selectedTodos);
}
/**
* Set current todo by id
* @param id
*/
setCurrentTodo(id)
{
this.currentTodo = this.todos.find(todo => {
return todo.id === id;
});
this.onCurrentTodoChanged.next(this.currentTodo);
}
/**
* Toggle tag on selected todos
* @param tagId
*/
toggleTagOnSelectedTodos(tagId)
{
this.selectedTodos.map(todo => {
this.toggleTagOnTodo(tagId, todo);
});
}
toggleTagOnTodo(tagId, todo)
{
const index = todo.tags.indexOf(tagId);
if ( index !== -1 )
{
todo.tags.splice(index, 1);
}
else
{
todo.tags.push(tagId);
}
this.updateTodo(todo);
}
/**
* Update the todo
* @param todo
* @returns {Promise<any>}
*/
updateTodo(todo)
{
return new Promise((resolve, reject) => {
this.http.post('api/todo-todos/' + todo.id, {...todo})
.subscribe(response => {
this.getTodos().then(todos => {
if ( todos && this.currentTodo )
{
this.setCurrentTodo(this.currentTodo.id);
}
resolve(todos);
}, reject);
});
});
}
}