From 46ad46bb0222d1e83209cc6e1ba5840d47c49427 Mon Sep 17 00:00:00 2001 From: mustafahlvc Date: Thu, 27 Jul 2017 00:33:17 +0300 Subject: [PATCH] (Todo App) Todo app created, drag drop libarary added, (Chat App) Chat app module name changed. --- package-lock.json | 51 +++ package.json | 1 + src/app/app.module.ts | 8 +- src/app/core/modules/shared.module.ts | 7 +- src/app/fuse-fake-db/fuse-fake-db.service.ts | 16 +- src/app/fuse-fake-db/todo.ts | 324 ++++++++++++++++ src/app/main/apps/chat/chat.module.ts | 2 +- src/app/main/apps/chat/chat.service.ts | 2 +- .../sidenavs/main/main-sidenav.component.html | 55 +++ .../sidenavs/main/main-sidenav.component.scss | 30 ++ .../sidenavs/main/main-sidenav.component.ts | 54 +++ .../todo-details/todo-details.component.html | 102 +++++ .../todo-details/todo-details.component.scss | 103 +++++ .../todo-details/todo-details.component.ts | 128 +++++++ .../todo-list-item.component.html | 93 +++++ .../todo-list-item.component.scss | 119 ++++++ .../todo-list-item.component.ts | 107 ++++++ .../todo/todo-list/todo-list.component.html | 13 + .../todo/todo-list/todo-list.component.scss | 12 + .../todo/todo-list/todo-list.component.ts | 102 +++++ src/app/main/apps/todo/todo.component.html | 98 +++++ src/app/main/apps/todo/todo.component.scss | 53 +++ src/app/main/apps/todo/todo.component.ts | 77 ++++ src/app/main/apps/todo/todo.model.ts | 56 +++ src/app/main/apps/todo/todo.module.ts | 78 ++++ src/app/main/apps/todo/todo.service.ts | 352 ++++++++++++++++++ 26 files changed, 2025 insertions(+), 18 deletions(-) create mode 100644 src/app/fuse-fake-db/todo.ts create mode 100644 src/app/main/apps/todo/sidenavs/main/main-sidenav.component.html create mode 100644 src/app/main/apps/todo/sidenavs/main/main-sidenav.component.scss create mode 100644 src/app/main/apps/todo/sidenavs/main/main-sidenav.component.ts create mode 100644 src/app/main/apps/todo/todo-details/todo-details.component.html create mode 100644 src/app/main/apps/todo/todo-details/todo-details.component.scss create mode 100644 src/app/main/apps/todo/todo-details/todo-details.component.ts create mode 100644 src/app/main/apps/todo/todo-list/todo-list-item/todo-list-item.component.html create mode 100644 src/app/main/apps/todo/todo-list/todo-list-item/todo-list-item.component.scss create mode 100644 src/app/main/apps/todo/todo-list/todo-list-item/todo-list-item.component.ts create mode 100644 src/app/main/apps/todo/todo-list/todo-list.component.html create mode 100644 src/app/main/apps/todo/todo-list/todo-list.component.scss create mode 100644 src/app/main/apps/todo/todo-list/todo-list.component.ts create mode 100644 src/app/main/apps/todo/todo.component.html create mode 100644 src/app/main/apps/todo/todo.component.scss create mode 100644 src/app/main/apps/todo/todo.component.ts create mode 100644 src/app/main/apps/todo/todo.model.ts create mode 100644 src/app/main/apps/todo/todo.module.ts create mode 100644 src/app/main/apps/todo/todo.service.ts diff --git a/package-lock.json b/package-lock.json index f85e9644..def335a9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index 7fcb4f58..e1647d2f 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 6c28fef6..d2849428 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -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' diff --git a/src/app/core/modules/shared.module.ts b/src/app/core/modules/shared.module.ts index bfb98650..8a2ce09e 100644 --- a/src/app/core/modules/shared.module.ts +++ b/src/app/core/modules/shared.module.ts @@ -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] }) diff --git a/src/app/fuse-fake-db/fuse-fake-db.service.ts b/src/app/fuse-fake-db/fuse-fake-db.service.ts index bc80d113..3e9d4f02 100644 --- a/src/app/fuse-fake-db/fuse-fake-db.service.ts +++ b/src/app/fuse-fake-db/fuse-fake-db.service.ts @@ -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 }; } diff --git a/src/app/fuse-fake-db/todo.ts b/src/app/fuse-fake-db/todo.ts new file mode 100644 index 00000000..88121a98 --- /dev/null +++ b/src/app/fuse-fake-db/todo.ts @@ -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' + } + ]; +} diff --git a/src/app/main/apps/chat/chat.module.ts b/src/app/main/apps/chat/chat.module.ts index 4cb3e4e5..42d843c5 100644 --- a/src/app/main/apps/chat/chat.module.ts +++ b/src/app/main/apps/chat/chat.module.ts @@ -39,6 +39,6 @@ const routes: Routes = [ ChatService ] }) -export class ChatModule +export class FuseChatModule { } diff --git a/src/app/main/apps/chat/chat.service.ts b/src/app/main/apps/chat/chat.service.ts index 78638753..59998e4b 100644 --- a/src/app/main/apps/chat/chat.service.ts +++ b/src/app/main/apps/chat/chat.service.ts @@ -178,7 +178,7 @@ export class ChatService implements Resolve } /** - * The Mail App Main Resolver + * The Chat App Main Resolver * @param {ActivatedRouteSnapshot} route * @param {RouterStateSnapshot} state * @returns {Observable | Promise | any} diff --git a/src/app/main/apps/todo/sidenavs/main/main-sidenav.component.html b/src/app/main/apps/todo/sidenavs/main/main-sidenav.component.html new file mode 100644 index 00000000..e0476296 --- /dev/null +++ b/src/app/main/apps/todo/sidenavs/main/main-sidenav.component.html @@ -0,0 +1,55 @@ + +
+ + + + +
+ + + + + diff --git a/src/app/main/apps/todo/sidenavs/main/main-sidenav.component.scss b/src/app/main/apps/todo/sidenavs/main/main-sidenav.component.scss new file mode 100644 index 00000000..4e646178 --- /dev/null +++ b/src/app/main/apps/todo/sidenavs/main/main-sidenav.component.scss @@ -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; + } +} diff --git a/src/app/main/apps/todo/sidenavs/main/main-sidenav.component.ts b/src/app/main/apps/todo/sidenavs/main/main-sidenav.component.ts new file mode 100644 index 00000000..c84e6deb --- /dev/null +++ b/src/app/main/apps/todo/sidenavs/main/main-sidenav.component.ts @@ -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(); + } +} diff --git a/src/app/main/apps/todo/todo-details/todo-details.component.html b/src/app/main/apps/todo/todo-details/todo-details.component.html new file mode 100644 index 00000000..fc4ad78f --- /dev/null +++ b/src/app/main/apps/todo/todo-details/todo-details.component.html @@ -0,0 +1,102 @@ +
+ check_box + Select a todo +
+ +
+ +
+ + + +
+ + + + + + + + + + + + + +
+
+ +
+ +
+ + + + + +
+
{{tags | getById:tagId:'title'}} +
+
+ +
+ + + + + + + + + + + + +
+ + + + +
+ +
+
diff --git a/src/app/main/apps/todo/todo-details/todo-details.component.scss b/src/app/main/apps/todo/todo-details/todo-details.component.scss new file mode 100644 index 00000000..aed07e55 --- /dev/null +++ b/src/app/main/apps/todo/todo-details/todo-details.component.scss @@ -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; + } + } + } +} diff --git a/src/app/main/apps/todo/todo-details/todo-details.component.ts b/src/app/main/apps/todo/todo-details/todo-details.component.ts new file mode 100644 index 00000000..59a3c3c0 --- /dev/null +++ b/src/app/main/apps/todo/todo-details/todo-details.component.ts @@ -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(); + } +} diff --git a/src/app/main/apps/todo/todo-list/todo-list-item/todo-list-item.component.html b/src/app/main/apps/todo/todo-list/todo-list-item/todo-list-item.component.html new file mode 100644 index 00000000..5e1e2721 --- /dev/null +++ b/src/app/main/apps/todo/todo-list/todo-list-item/todo-list-item.component.html @@ -0,0 +1,93 @@ +
+ + drag_handle + + + + + +
+ +
+ +
+ {{todo.title}} +
+ +
+ {{todo.notes}} +
+ +
+ +
+ +
+ +
{{tags | getById:tagId:'title'}}
+
+ +
+ +
+ +
+ + + + + + + + + + + + + + + +
+ +
+ +
diff --git a/src/app/main/apps/todo/todo-list/todo-list-item/todo-list-item.component.scss b/src/app/main/apps/todo/todo-list/todo-list-item/todo-list-item.component.scss new file mode 100644 index 00000000..730811e2 --- /dev/null +++ b/src/app/main/apps/todo/todo-list/todo-list-item/todo-list-item.component.scss @@ -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); + } +} diff --git a/src/app/main/apps/todo/todo-list/todo-list-item/todo-list-item.component.ts b/src/app/main/apps/todo/todo-list/todo-list-item/todo-list-item.component.ts new file mode 100644 index 00000000..4a3003f5 --- /dev/null +++ b/src/app/main/apps/todo/todo-list/todo-list-item/todo-list-item.component.ts @@ -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); + } +} diff --git a/src/app/main/apps/todo/todo-list/todo-list.component.html b/src/app/main/apps/todo/todo-list/todo-list.component.html new file mode 100644 index 00000000..1c630129 --- /dev/null +++ b/src/app/main/apps/todo/todo-list/todo-list.component.html @@ -0,0 +1,13 @@ +
+ There are no todos! +
+
+ + +
diff --git a/src/app/main/apps/todo/todo-list/todo-list.component.scss b/src/app/main/apps/todo/todo-list/todo-list.component.scss new file mode 100644 index 00000000..cef45a4a --- /dev/null +++ b/src/app/main/apps/todo/todo-list/todo-list.component.scss @@ -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 { + +} + diff --git a/src/app/main/apps/todo/todo-list/todo-list.component.ts b/src/app/main/apps/todo/todo-list/todo-list.component.ts new file mode 100644 index 00000000..9a26a6b5 --- /dev/null +++ b/src/app/main/apps/todo/todo-list/todo-list.component.ts @@ -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); + } +} diff --git a/src/app/main/apps/todo/todo.component.html b/src/app/main/apps/todo/todo.component.html new file mode 100644 index 00000000..89ef2257 --- /dev/null +++ b/src/app/main/apps/todo/todo.component.html @@ -0,0 +1,98 @@ +
+ + +
+ + + + + + + + + + + +
+ + +
+ +
+ + + + +
+ +
+ + + +
+ + +
+ + + + + + + + + + + + + + + +
+ + + + + + +
+ + + +
+ +
+ + + + + +
+ +
+ + +
+ + +
+ + +
+ +
diff --git a/src/app/main/apps/todo/todo.component.scss b/src/app/main/apps/todo/todo.component.scss new file mode 100644 index 00000000..b3d82494 --- /dev/null +++ b/src/app/main/apps/todo/todo.component.scss @@ -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; + } + } + } +} + diff --git a/src/app/main/apps/todo/todo.component.ts b/src/app/main/apps/todo/todo.component.ts new file mode 100644 index 00000000..2a7702c4 --- /dev/null +++ b/src/app/main/apps/todo/todo.component.ts @@ -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); + } + +} diff --git a/src/app/main/apps/todo/todo.model.ts b/src/app/main/apps/todo/todo.model.ts new file mode 100644 index 00000000..4dba7f5f --- /dev/null +++ b/src/app/main/apps/todo/todo.model.ts @@ -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; + } +} diff --git a/src/app/main/apps/todo/todo.module.ts b/src/app/main/apps/todo/todo.module.ts new file mode 100644 index 00000000..2fe6b088 --- /dev/null +++ b/src/app/main/apps/todo/todo.module.ts @@ -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 +{ +} diff --git a/src/app/main/apps/todo/todo.service.ts b/src/app/main/apps/todo/todo.service.ts new file mode 100644 index 00000000..4039a4b4 --- /dev/null +++ b/src/app/main/apps/todo/todo.service.ts @@ -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 +{ + todos: Todo[]; + selectedTodos: Todo[]; + currentTodo: Todo; + + filters: any[]; + tags: any[]; + routeParams: any; + + onTodosChanged: BehaviorSubject = new BehaviorSubject([]); + onSelectedTodosChanged: BehaviorSubject = new BehaviorSubject([]); + onCurrentTodoChanged: BehaviorSubject = new BehaviorSubject([]); + + onFiltersChanged: BehaviorSubject = new BehaviorSubject([]); + onTagsChanged: BehaviorSubject = new BehaviorSubject([]); + + constructor(private http: Http) + { + this.selectedTodos = []; + } + + /** + * Resolve + * @param {ActivatedRouteSnapshot} route + * @param {RouterStateSnapshot} state + * @returns {Observable | Promise | any} + */ + resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable | Promise | 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} + */ + getFilters(): Promise + { + 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} + */ + getTags(): Promise + { + 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} + */ + getTodos(): Promise + { + 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} + */ + getTodosByParams(handle): Promise + { + 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} + */ + getTodosByFilter(handle): Promise + { + + 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} + */ + getTodosByTag(handle): Promise + { + 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} + */ + 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); + }); + }); + } +}