diff --git a/README.md b/README.md index 8fc6118..b58c3e5 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,19 @@ -# angular2byexample -Source code repository for the book "Angular2 by Example" +# Angular2 By Example + +[![Angular2 By Example Front Cover](https://d1ldz4te4covpm.cloudfront.net/sites/default/files/imagecache/ppv4_main_book_cover/B05079_MockupCover_Normal.jpg)](https://www.packtpub.com/web-development/angular-2-example) + +Source code repository for the book [Angular2 by Example](https://www.packtpub.com/web-development/angular-2-example) + +**Releasing soon!!** + +To setup code for Guess The Number see the README.md in **guessthenumber** folder. + +To setup code for Personal Trainer see the README.md in **trainer** folder. + +## Note + +The **master** branch contains the final outcome for both the samples we build throughout the book. + +Chapter progress is tracked using individual branches. Each **chapter has checkpoints** and each checkpoint code is **available on a seperate branch**. + +For example, branches *base*, *checkpoint2.1*, *checkpoint2.2*, *checkpoint2.3* and *checkpoint2.4* contain code checkpoints for **Chapter 2**. diff --git a/guessthenumber/app.ts b/guessthenumber/app.ts deleted file mode 100644 index 2621c59..0000000 --- a/guessthenumber/app.ts +++ /dev/null @@ -1,48 +0,0 @@ -//import 'zone.js'; -//import 'reflect-metadata'; -import {Component, View}from 'angular2/core'; -import {NgIf} from 'angular2/common'; -import {bootstrap} from 'angular2/platform/browser'; -@Component({ - selector: 'my-app', - template: ` -
-

Guess the Number !

-

Guess the computer generated random number between 1 and 1000.

- - - - -
-

Your guess is higher.

-

Your guess is lower.

-

Yes! That"s it.

-
-

No of guesses : - {{noOfTries}} -

-
-` -}) -class GuessTheNumberComponent { - deviation: number; - noOfTries: number; - original: number; - guess: number; - - constructor() { - this.initializeGame(); - } - verifyGuess() { - this.deviation = this.original - this.guess; - this.noOfTries = this.noOfTries + 1; - } - initializeGame() { - this.noOfTries = 0; - this.original = Math.floor((Math.random() * 1000) + 1); - this.guess = null; - this.deviation = null; - } -} - -bootstrap(GuessTheNumberComponent); diff --git a/guessthenumber/app/app.module.ts b/guessthenumber/app/app.module.ts new file mode 100644 index 0000000..710d89b --- /dev/null +++ b/guessthenumber/app/app.module.ts @@ -0,0 +1,12 @@ +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; + +import { GuessTheNumberComponent } from './guess-the-number.component'; + +@NgModule({ + imports: [ BrowserModule ], + declarations: [ GuessTheNumberComponent ], + bootstrap: [ GuessTheNumberComponent ] +}) + +export class AppModule { } diff --git a/guessthenumber/app/guess-the-number.component.ts b/guessthenumber/app/guess-the-number.component.ts new file mode 100644 index 0000000..0992fb2 --- /dev/null +++ b/guessthenumber/app/guess-the-number.component.ts @@ -0,0 +1,43 @@ +import { Component }from '@angular/core'; + +@Component({ + selector: 'my-app', + template: ` +
+

Guess the Number !

+

Guess the computer generated random number between 1 and 1000.

+ + + + +
+

Your guess is higher.

+

Your guess is lower.

+

Yes! That's it.

+
+

No of guesses : + {{noOfTries}} +

+
+ ` +}) +export class GuessTheNumberComponent { + deviation: number; + noOfTries: number; + original: number; + guess: number; + + constructor() { + this.initializeGame(); + } + initializeGame() { + this.noOfTries = 0; + this.original = Math.floor((Math.random() * 1000) + 1); + this.guess = null; + this.deviation = null; + } + verifyGuess() { + this.deviation = this.original - this.guess; + this.noOfTries = this.noOfTries + 1; + } +} \ No newline at end of file diff --git a/guessthenumber/app/main.ts b/guessthenumber/app/main.ts new file mode 100644 index 0000000..44f34b2 --- /dev/null +++ b/guessthenumber/app/main.ts @@ -0,0 +1,6 @@ +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; + +import { AppModule } from './app.module'; + +const platform = platformBrowserDynamic(); +platform.bootstrapModule(AppModule); diff --git a/guessthenumber/index.html b/guessthenumber/index.html index 555fdf0..c351c05 100644 --- a/guessthenumber/index.html +++ b/guessthenumber/index.html @@ -1,27 +1,19 @@ - - - Guess the Number! - - - - - - - - - - - Loading... - - + + Guess the Number! + + + + + + + + + + + Loading... + diff --git a/guessthenumber/systemjs.config.js b/guessthenumber/systemjs.config.js new file mode 100644 index 0000000..c7fa815 --- /dev/null +++ b/guessthenumber/systemjs.config.js @@ -0,0 +1,24 @@ +System.config({ + map : { + 'app': 'app', + 'rxjs': 'https://unpkg.com/rxjs@5.0.0-beta.12', + '@angular/common': 'https://unpkg.com/@angular/common@2.0.0', + '@angular/compiler': 'https://unpkg.com/@angular/compiler@2.0.0', + '@angular/core': 'https://unpkg.com/@angular/core@2.0.0', + '@angular/platform-browser': 'https://unpkg.com/@angular/platform-browser@2.0.0', + '@angular/platform-browser-dynamic': 'https://unpkg.com/@angular/platform-browser-dynamic@2.0.0' + }, + packages:{ + 'app': { main: 'main.ts', defaultExtension: 'ts' }, + '@angular/common': { main: 'bundles/common.umd.js', defaultExtension: 'js' }, + '@angular/compiler': { main: 'bundles/compiler.umd.js', defaultExtension: 'js' }, + '@angular/core': { main: 'bundles/core.umd.js', defaultExtension: 'js' }, + '@angular/platform-browser': { main: 'bundles/platform-browser.umd.js', defaultExtension: 'js' }, + '@angular/platform-browser-dynamic': { main: 'bundles/platform-browser-dynamic.umd.js', defaultExtension: 'js' }, + }, + // DEMO ONLY! REAL CODE SHOULD NOT TRANSPILE IN THE BROWSER + transpiler: 'typescript', + typescriptOptions: { + emitDecoratorMetadata: true + } +}); \ No newline at end of file diff --git a/trainer/.gitignore b/trainer/.gitignore index f06235c..c5ac28a 100644 --- a/trainer/.gitignore +++ b/trainer/.gitignore @@ -1,2 +1,3 @@ node_modules dist +typings/** \ No newline at end of file diff --git a/trainer/README.md b/trainer/README.md index e71f751..e5374cd 100644 --- a/trainer/README.md +++ b/trainer/README.md @@ -14,3 +14,10 @@ Clone this repo and execute in your favourite shell: After completing installation type in your favourite shell: * `gulp play` to start the app in a new browser window. App files are observed and will be re-transpiled on each change. + +> ~~If you see a bunch of **TypeScript** compilation errors while running `gulp play`, the required **typings** did not get installed. While `npm install` should also install the typings, at times this does not happen. +> If the typing installation throws error try to upgrade the typing global installation with command `npm install typings -g` and then run the command `npm run typings install` again.~~ + +> The old approach of using the `typings` tool to install typings has been abandoned in favour of **npm** based typing supported by new versions of **TypeScript** compiler. Take latest of the code and upgrade to latest version of TypeScript compiler. `npm install` should now install all typings. + +> **Note**: The book content still show use of `typings`, you can disregard it. diff --git a/trainer/gulpfile.js b/trainer/gulpfile.js index c5de7e8..d0e205d 100644 --- a/trainer/gulpfile.js +++ b/trainer/gulpfile.js @@ -1,45 +1,56 @@ var gulp = require('gulp'); +var connect = require('gulp-connect'); var PATHS = { - src: 'src/**/*.ts' + src: 'src/**/*.ts', + html: 'src/**/*.html', + css: 'src/**/*.css' }; -gulp.task('clean', function (done) { - var del = require('del'); - del(['dist'], done); +gulp.task('clean', function(done) { + var del = require('del'); + del(['dist'], done); }); -gulp.task('ts2js', function () { - var typescript = require('gulp-typescript'); - var sourcemaps = require('gulp-sourcemaps'); - - var tsResult = gulp.src(PATHS.src) - .pipe(sourcemaps.init()) - .pipe(typescript({ - noImplicitAny: true, - module: 'system', - target: 'ES5', - moduleResolution: 'node', - emitDecoratorMetadata: true, - experimentalDecorators: true - })); - - return tsResult.js - .pipe(sourcemaps.write()) - .pipe(gulp.dest('dist')); +gulp.task('ts2js', function() { + var typescript = require('gulp-typescript'); + var sourcemaps = require('gulp-sourcemaps'); + + var tsResult = gulp.src(PATHS.src) + .pipe(sourcemaps.init()) + .pipe(typescript({ + noImplicitAny: true, + module: 'system', + target: 'ES5', + moduleResolution: 'node', + emitDecoratorMetadata: true, + experimentalDecorators: true + })); + + return tsResult.js + .pipe(sourcemaps.write()) + .pipe(gulp.dest('dist')) + .pipe(connect.reload()); }); -gulp.task('play', ['ts2js'], function () { - var http = require('http'); - var connect = require('connect'); - var serveStatic = require('serve-static'); - var open = require('open'); +gulp.task('play', ['ts2js'], function() { + var http = require('http'); + var open = require('open'); + var watch = require('gulp-watch'); - var port = 9000, app; - gulp.watch(PATHS.src, ['ts2js']); - app = connect().use(serveStatic(__dirname)); - http.createServer(app).listen(port, function () { - open('http://localhost:' + port); - }); + var port = 9000, + app; + + connect.server({ + root: __dirname, + port: port, + livereload: true, + fallback: 'index.html' + }); + open('http://localhost:' + port + '/index.html'); + + gulp.watch(PATHS.src, ['ts2js']); + watch(PATHS.html).pipe(connect.reload()); + watch(PATHS.css).pipe(connect.reload()); }); diff --git a/trainer/index.html b/trainer/index.html index 1aacbc7..862f163 100644 --- a/trainer/index.html +++ b/trainer/index.html @@ -8,37 +8,26 @@ - + - -
- Loading... -
+ Loading... - - + + + + + + + + - - - - - - + diff --git a/trainer/package.json b/trainer/package.json index 5f9cae2..771a759 100644 --- a/trainer/package.json +++ b/trainer/package.json @@ -11,18 +11,32 @@ }, "homepage": "https://github.com/chandermani/angular2byexample.git#readme", "devDependencies": { - "connect": "^3.4.0", + "@types/core-js": "0.9.36", "del": "^1.2.0", "gulp": "^3.9.0", - "gulp-typescript": "^2.8.0", + "gulp-connect": "^3.1.0", "gulp-sourcemaps": "^1.6.0", + "gulp-typescript": "^3.1.5", + "gulp-watch": "^4.3.5", + "gulp-webserver": "^0.9.1", "open": "0.0.5", - "serve-static": "^1.10.0" + "typescript": "^2.2.1" }, "dependencies": { - "angular2": "2.0.0-beta.0", - "es6-shim": "^0.33.6", - "rxjs": "5.0.0-beta.0", - "systemjs": "0.19.6" - } -} + "@angular/common": "2.0.0", + "@angular/compiler": "2.0.0", + "@angular/core": "2.0.0", + "@angular/forms": "2.0.0", + "@angular/http": "2.0.0", + "@angular/platform-browser": "2.0.0", + "@angular/platform-browser-dynamic": "2.0.0", + "@angular/router": "3.0.0", + "angular2-modal": "2.0.0-beta.13", + "core-js": "^2.4.1", + "reflect-metadata": "0.1.3", + "rxjs": "5.0.0-beta.12", + "systemjs": "0.19.27", + "zone.js": "^0.6.23" + }, + "scripts": {} +} \ No newline at end of file diff --git a/trainer/src/bootstrap.ts b/trainer/src/bootstrap.ts index f333f6e..8a2b929 100644 --- a/trainer/src/bootstrap.ts +++ b/trainer/src/bootstrap.ts @@ -1,4 +1,3 @@ -import {bootstrap} from 'angular2/platform/browser'; -import {TrainerApp} from './components/app/app'; -import {ROUTER_PROVIDERS} from 'angular2/router'; -bootstrap(TrainerApp, [ROUTER_PROVIDERS]); +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { AppModule } from './components/app/app.module'; +platformBrowserDynamic().bootstrapModule(AppModule); diff --git a/trainer/src/components/app/app.component.ts b/trainer/src/components/app/app.component.ts new file mode 100644 index 0000000..c20101d --- /dev/null +++ b/trainer/src/components/app/app.component.ts @@ -0,0 +1,21 @@ +import {Component, ViewContainerRef} from '@angular/core'; +import { Overlay } from 'angular2-modal'; + +import {WorkoutHistoryComponent} from '../workout-history/workout-history.component'; + +@Component({ + selector: 'trainer-app', + template: ` +
+ +
` +}) +export class TrainerAppComponent { + constructor(overlay: Overlay, viewContainer: ViewContainerRef) { + overlay.defaultViewContainer = viewContainer; + } +} diff --git a/trainer/src/components/app/app.module.ts b/trainer/src/components/app/app.module.ts new file mode 100644 index 0000000..f1a6a4c --- /dev/null +++ b/trainer/src/components/app/app.module.ts @@ -0,0 +1,36 @@ +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; + +import { TrainerAppComponent } from './app.component'; +import {HeaderComponent} from './header.component'; + +import {WorkoutRunnerModule} from '../workout-runner/workout-runner.module'; +import {StartModule} from '../start/start.module'; +import {FinishModule} from '../finish/finish.module'; +import {ServicesModule} from '../../services/services.module'; +import {WorkoutHistoryModule} from '../workout-history/workout-history.module'; +import {WorkoutBuilderModule} from '../workout-builder/workout-builder.module'; + +import { ModalModule } from 'angular2-modal'; +import { BootstrapModalModule } from 'angular2-modal/plugins/bootstrap'; + +import {routing} from './app.routes'; + +@NgModule({ + imports: [ + BrowserModule, + WorkoutRunnerModule, + StartModule, + FinishModule, + routing, + ModalModule.forRoot(), + BootstrapModalModule, + ServicesModule, + WorkoutHistoryModule, + WorkoutBuilderModule], + declarations: [ + TrainerAppComponent, + HeaderComponent], + bootstrap: [TrainerAppComponent] +}) +export class AppModule { } \ No newline at end of file diff --git a/trainer/src/components/app/app.routes.ts b/trainer/src/components/app/app.routes.ts new file mode 100644 index 0000000..f7b4cdc --- /dev/null +++ b/trainer/src/components/app/app.routes.ts @@ -0,0 +1,16 @@ +import { ModuleWithProviders } from '@angular/core'; +import { Routes, RouterModule } from '@angular/router'; +import {WorkoutContainerCompnent} from '../workout-runner/workout-container/workout-container.component'; +import {StartComponent} from '../start/start.component'; +import {FinishComponent} from '../finish/finish.component'; +import {WorkoutHistoryComponent} from '../workout-history/workout-history.component'; + +export const routes: Routes = [ + { path: 'start', component: StartComponent }, + { path: 'workout', component: WorkoutContainerCompnent }, + { path: 'finish', component: FinishComponent }, + { path: 'history', component: WorkoutHistoryComponent }, + { path: '**', redirectTo: '/start' } +]; + +export const routing: ModuleWithProviders = RouterModule.forRoot(routes); \ No newline at end of file diff --git a/trainer/src/components/app/app.ts b/trainer/src/components/app/app.ts deleted file mode 100644 index 6b2c3ca..0000000 --- a/trainer/src/components/app/app.ts +++ /dev/null @@ -1,17 +0,0 @@ -import {Component} from 'angular2/core'; -import {WorkoutRunner} from '../workout-runner/workout-runner'; -import {Start} from '../workout-runner/start'; -import {Finish} from '../workout-runner/finish'; -import {RouteConfig, ROUTER_DIRECTIVES} from 'angular2/router'; -@Component({ - selector: 'trainer-app', - directives: [ROUTER_DIRECTIVES], - template: `` -}) -@RouteConfig([ - { path: '/start', name: 'Start', component: Start, useAsDefault: true }, - { path: '/workout', name: 'Workout', component: WorkoutRunner }, - { path: '/finish', name: 'Finish', component: Finish } -]) -export class TrainerApp { -} diff --git a/trainer/src/components/app/header.component.ts b/trainer/src/components/app/header.component.ts new file mode 100644 index 0000000..fd9a7cb --- /dev/null +++ b/trainer/src/components/app/header.component.ts @@ -0,0 +1,21 @@ +import {Component} from '@angular/core'; +import {Router, Event } from '@angular/router'; + +@Component({ + selector: 'header', + template: ` + ` +}) +export class HeaderComponent { + showHistoryLink: boolean = true; + private subscription: any; + constructor(private router: Router) { + this.router.events.subscribe((data: Event) => { + this.showHistoryLink = !this.router.url.startsWith('/workout'); + }); + } +} \ No newline at end of file diff --git a/trainer/src/components/finish/finish.component.ts b/trainer/src/components/finish/finish.component.ts new file mode 100644 index 0000000..bcba3d0 --- /dev/null +++ b/trainer/src/components/finish/finish.component.ts @@ -0,0 +1,8 @@ +import {Component, Input} from '@angular/core'; + +@Component({ + selector: 'finish', + templateUrl: '/src/components/finish/finish.html', +}) +export class FinishComponent { +} diff --git a/trainer/src/components/workout-runner/finish.tpl.html b/trainer/src/components/finish/finish.html similarity index 91% rename from trainer/src/components/workout-runner/finish.tpl.html rename to trainer/src/components/finish/finish.html index ecd650a..ca71873 100644 --- a/trainer/src/components/workout-runner/finish.tpl.html +++ b/trainer/src/components/finish/finish.html @@ -6,7 +6,7 @@

Well Done!


- + Start Again! diff --git a/trainer/src/components/finish/finish.module.ts b/trainer/src/components/finish/finish.module.ts new file mode 100644 index 0000000..b3aa4fe --- /dev/null +++ b/trainer/src/components/finish/finish.module.ts @@ -0,0 +1,14 @@ +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { RouterModule } from '@angular/router'; + +import { FinishComponent } from './finish.component'; + +@NgModule({ + imports: [ + BrowserModule, + RouterModule], + declarations: [FinishComponent], + exports: [FinishComponent], +}) +export class FinishModule { } \ No newline at end of file diff --git a/trainer/src/components/shared/order-by.pipe.ts b/trainer/src/components/shared/order-by.pipe.ts new file mode 100644 index 0000000..13083d5 --- /dev/null +++ b/trainer/src/components/shared/order-by.pipe.ts @@ -0,0 +1,25 @@ +import {Pipe} from '@angular/core'; + +@Pipe({ + name: 'orderBy' +}) +export class OrderByPipe { + transform(value: Array, field:string): any { + if (value == null || value.length == 1) { + return value; + } + if (field.startsWith("-")) { + field = field.substring(1); + if (typeof value[0][field] === 'string' || value[0][field] instanceof String) { + return [...value].sort((a, b) => b[field].localeCompare(a[field])); + } + return [...value].sort((a, b) => b[field] - a[field]); + } + else { + if (typeof value[0][field] === 'string' || value[0][field] instanceof String) { + return [...value].sort((a, b) => -b[field].localeCompare(a[field])); + } + return [...value].sort((a, b) => a[field] - b[field]); + } + } +} diff --git a/trainer/src/components/shared/search.pipe.ts b/trainer/src/components/shared/search.pipe.ts new file mode 100644 index 0000000..44af95e --- /dev/null +++ b/trainer/src/components/shared/search.pipe.ts @@ -0,0 +1,13 @@ +import {Pipe} from '@angular/core'; + +@Pipe({ + name: 'search', + pure: false +}) +export class SearchPipe { + transform(value: Array, field:string, searchTerm:string): any { + if (!field) return []; + if (searchTerm == null) return [...value]; + return value.filter((item) => item[field] === searchTerm); + } +} \ No newline at end of file diff --git a/trainer/src/components/shared/shared.module.ts b/trainer/src/components/shared/shared.module.ts new file mode 100644 index 0000000..9d2cdc5 --- /dev/null +++ b/trainer/src/components/shared/shared.module.ts @@ -0,0 +1,15 @@ +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; + +import {OrderByPipe} from './order-by.pipe'; +import {SearchPipe} from './search.pipe'; + +@NgModule({ + imports: [], + declarations: [OrderByPipe, + SearchPipe], + exports: [ + OrderByPipe, + SearchPipe], +}) +export class SharedModule { } \ No newline at end of file diff --git a/trainer/src/components/start/start.component.ts b/trainer/src/components/start/start.component.ts new file mode 100644 index 0000000..e48ed55 --- /dev/null +++ b/trainer/src/components/start/start.component.ts @@ -0,0 +1,8 @@ +import {Component, Input} from '@angular/core'; + +@Component({ + selector: 'start', + templateUrl: '/src/components/start/start.html', +}) +export class StartComponent { +} diff --git a/trainer/src/components/workout-runner/start.tpl.html b/trainer/src/components/start/start.html similarity index 53% rename from trainer/src/components/workout-runner/start.tpl.html rename to trainer/src/components/start/start.html index 8de8d3e..1f9340b 100644 --- a/trainer/src/components/workout-runner/start.tpl.html +++ b/trainer/src/components/start/start.html @@ -5,12 +5,18 @@

Ready for a Workout?



-
diff --git a/trainer/src/components/start/start.module.ts b/trainer/src/components/start/start.module.ts new file mode 100644 index 0000000..9441ca1 --- /dev/null +++ b/trainer/src/components/start/start.module.ts @@ -0,0 +1,14 @@ +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { RouterModule } from '@angular/router'; + +import { StartComponent } from './start.component'; + +@NgModule({ + imports: [ + BrowserModule, + RouterModule], + declarations: [StartComponent], + exports: [StartComponent], +}) +export class StartModule { } \ No newline at end of file diff --git a/trainer/src/components/workout-builder/exercise/exercise.component.html b/trainer/src/components/workout-builder/exercise/exercise.component.html new file mode 100644 index 0000000..00dd45b --- /dev/null +++ b/trainer/src/components/workout-builder/exercise/exercise.component.html @@ -0,0 +1,7 @@ +
+
+
+

Exercise

+
+
+
\ No newline at end of file diff --git a/trainer/src/components/workout-builder/exercise/exercise.component.ts b/trainer/src/components/workout-builder/exercise/exercise.component.ts new file mode 100644 index 0000000..b7d03be --- /dev/null +++ b/trainer/src/components/workout-builder/exercise/exercise.component.ts @@ -0,0 +1,8 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'exercise', + templateUrl: '/src/components/workout-builder/exercise/exercise.component.html', +}) +export class ExerciseComponent{ +} diff --git a/trainer/src/components/workout-builder/exercises/exercises.component.html b/trainer/src/components/workout-builder/exercises/exercises.component.html new file mode 100644 index 0000000..37a23b8 --- /dev/null +++ b/trainer/src/components/workout-builder/exercises/exercises.component.html @@ -0,0 +1,5 @@ +
+
+

Exercises

+
+
\ No newline at end of file diff --git a/trainer/src/components/workout-builder/exercises/exercises.component.ts b/trainer/src/components/workout-builder/exercises/exercises.component.ts new file mode 100644 index 0000000..8f7a494 --- /dev/null +++ b/trainer/src/components/workout-builder/exercises/exercises.component.ts @@ -0,0 +1,9 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'exercises', + templateUrl: '/src/components/workout-builder/exercises/exercises.component.html' +}) +export class ExercisesComponent{ +} + diff --git a/trainer/src/components/workout-builder/navigation/left-nav-exercises.component.html b/trainer/src/components/workout-builder/navigation/left-nav-exercises.component.html new file mode 100644 index 0000000..dd9279f --- /dev/null +++ b/trainer/src/components/workout-builder/navigation/left-nav-exercises.component.html @@ -0,0 +1,3 @@ +
+

Exercises

+
\ No newline at end of file diff --git a/trainer/src/components/workout-builder/navigation/left-nav-exercises.component.ts b/trainer/src/components/workout-builder/navigation/left-nav-exercises.component.ts new file mode 100644 index 0000000..3c0b497 --- /dev/null +++ b/trainer/src/components/workout-builder/navigation/left-nav-exercises.component.ts @@ -0,0 +1,8 @@ +import {Component} from '@angular/core'; + +@Component({ + selector: 'left-nav-exercises', + templateUrl: '/src/components/workout-builder/navigation/left-nav-exercises.component.html' +}) +export class LeftNavExercisesComponent{ +} \ No newline at end of file diff --git a/trainer/src/components/workout-builder/navigation/left-nav-main.component.html b/trainer/src/components/workout-builder/navigation/left-nav-main.component.html new file mode 100644 index 0000000..20dd068 --- /dev/null +++ b/trainer/src/components/workout-builder/navigation/left-nav-main.component.html @@ -0,0 +1,7 @@ +
+
+
+

Left Nav Main

+
+
+
\ No newline at end of file diff --git a/trainer/src/components/workout-builder/navigation/left-nav-main.component.ts b/trainer/src/components/workout-builder/navigation/left-nav-main.component.ts new file mode 100644 index 0000000..ca8edf5 --- /dev/null +++ b/trainer/src/components/workout-builder/navigation/left-nav-main.component.ts @@ -0,0 +1,8 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'left-nav-main', + templateUrl: '/src/components/workout-builder/navigation/left-nav-main.component.html' +}) +export class LeftNavMainComponent{ +} \ No newline at end of file diff --git a/trainer/src/components/workout-builder/navigation/sub-nav.component.html b/trainer/src/components/workout-builder/navigation/sub-nav.component.html new file mode 100644 index 0000000..f6a6c41 --- /dev/null +++ b/trainer/src/components/workout-builder/navigation/sub-nav.component.html @@ -0,0 +1 @@ +
Sub Nav
\ No newline at end of file diff --git a/trainer/src/components/workout-builder/navigation/sub-nav.component.ts b/trainer/src/components/workout-builder/navigation/sub-nav.component.ts new file mode 100644 index 0000000..28131cc --- /dev/null +++ b/trainer/src/components/workout-builder/navigation/sub-nav.component.ts @@ -0,0 +1,8 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'sub-nav', + templateUrl: '/src/components/workout-builder/navigation/sub-nav.component.html' +}) +export class SubNavComponent{ +} \ No newline at end of file diff --git a/trainer/src/components/workout-builder/workout-builder.component.ts b/trainer/src/components/workout-builder/workout-builder.component.ts new file mode 100644 index 0000000..6943f7c --- /dev/null +++ b/trainer/src/components/workout-builder/workout-builder.component.ts @@ -0,0 +1,12 @@ +import { Component } from '@angular/core'; + +@Component({ + template: ` +
+ +
` + }) +export class WorkoutBuilderComponent{ +} \ No newline at end of file diff --git a/trainer/src/components/workout-builder/workout-builder.module.ts b/trainer/src/components/workout-builder/workout-builder.module.ts new file mode 100644 index 0000000..c7fe6b7 --- /dev/null +++ b/trainer/src/components/workout-builder/workout-builder.module.ts @@ -0,0 +1,32 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; + +import { ExerciseComponent } from "./exercise/exercise.component"; +import { ExercisesComponent } from "./exercises/exercises.component"; +import { LeftNavExercisesComponent } from "./navigation/left-nav-exercises.component"; +import { LeftNavMainComponent } from "./navigation/left-nav-main.component"; +import { SubNavComponent} from './navigation/sub-nav.component'; +import { WorkoutBuilderComponent } from "./workout-builder.component"; +import { WorkoutComponent } from "./workout/workout.component"; +import { WorkoutsComponent } from "./workouts/workouts.component"; + +import { workoutBuilderRouting } from './workout-builder.routes'; + +@NgModule({ + imports: [ + CommonModule, + workoutBuilderRouting + ], + declarations: [ + WorkoutBuilderComponent, + WorkoutComponent, + WorkoutsComponent, + ExerciseComponent, + ExercisesComponent, + SubNavComponent, + LeftNavExercisesComponent, + LeftNavMainComponent + ], + exports: [WorkoutBuilderComponent], +}) +export class WorkoutBuilderModule { } \ No newline at end of file diff --git a/trainer/src/components/workout-builder/workout-builder.routes.ts b/trainer/src/components/workout-builder/workout-builder.routes.ts new file mode 100644 index 0000000..feca23d --- /dev/null +++ b/trainer/src/components/workout-builder/workout-builder.routes.ts @@ -0,0 +1,26 @@ +import { ModuleWithProviders } from '@angular/core'; +import { Routes, RouterModule } from '@angular/router'; + +import { WorkoutBuilderComponent} from "./workout-builder.component"; +import { ExerciseComponent} from './exercise/exercise.component'; +import { ExercisesComponent} from './exercises/exercises.component'; +import { WorkoutComponent} from './workout/workout.component'; +import { WorkoutsComponent} from './workouts/workouts.component'; + +export const workoutBuilderRoutes: Routes = [ + { + path: 'builder', + component: WorkoutBuilderComponent, + children: [ + {path:'', pathMatch: 'full', redirectTo: 'workouts'}, + {path:'workouts', component: WorkoutsComponent }, + {path:'workout/new', component: WorkoutComponent }, + {path:'workout/:id', component: WorkoutComponent }, + {path:'exercises', component: ExercisesComponent}, + {path:'exercise/new', component: ExerciseComponent }, + {path:'exercise/:id', component: ExerciseComponent } + ] + } +]; + +export const workoutBuilderRouting: ModuleWithProviders = RouterModule.forChild(workoutBuilderRoutes); \ No newline at end of file diff --git a/trainer/src/components/workout-builder/workout/workout.component.html b/trainer/src/components/workout-builder/workout/workout.component.html new file mode 100644 index 0000000..fa7b834 --- /dev/null +++ b/trainer/src/components/workout-builder/workout/workout.component.html @@ -0,0 +1,7 @@ +
+
+
+

Workout

+
+
+
\ No newline at end of file diff --git a/trainer/src/components/workout-builder/workout/workout.component.ts b/trainer/src/components/workout-builder/workout/workout.component.ts new file mode 100644 index 0000000..1222de7 --- /dev/null +++ b/trainer/src/components/workout-builder/workout/workout.component.ts @@ -0,0 +1,8 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'workout', + templateUrl: '/src/components/workout-builder/workout/workout.component.html' +}) +export class WorkoutComponent{ +} \ No newline at end of file diff --git a/trainer/src/components/workout-builder/workouts/workouts.component.html b/trainer/src/components/workout-builder/workouts/workouts.component.html new file mode 100644 index 0000000..59ab755 --- /dev/null +++ b/trainer/src/components/workout-builder/workouts/workouts.component.html @@ -0,0 +1,7 @@ +
+
+
+

Workouts

+
+
+
\ No newline at end of file diff --git a/trainer/src/components/workout-builder/workouts/workouts.component.ts b/trainer/src/components/workout-builder/workouts/workouts.component.ts new file mode 100644 index 0000000..009129d --- /dev/null +++ b/trainer/src/components/workout-builder/workouts/workouts.component.ts @@ -0,0 +1,8 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'workouts', + templateUrl: '/src/components/workout-builder/workouts/workouts.component.html' +}) +export class WorkoutsComponent{ +} \ No newline at end of file diff --git a/trainer/src/components/workout-history/workout-history.component.ts b/trainer/src/components/workout-history/workout-history.component.ts new file mode 100644 index 0000000..5b8d647 --- /dev/null +++ b/trainer/src/components/workout-history/workout-history.component.ts @@ -0,0 +1,21 @@ +import {Component} from '@angular/core'; +import {WorkoutHistoryTracker, WorkoutLogEntry} from '../../services/workout-history-tracker'; +import {Location} from '@angular/common'; + +@Component({ + selector: 'workout-history', + templateUrl: `/src/components/workout-history/workout-history.html` +}) +export class WorkoutHistoryComponent { + history: Array = []; + completed: boolean; + constructor(private tracker: WorkoutHistoryTracker, private location: Location) { } + + ngOnInit() { + this.history = this.tracker.getHistory(); + } + + goBack() { + this.location.back(); + } +} diff --git a/trainer/src/components/workout-history/workout-history.html b/trainer/src/components/workout-history/workout-history.html new file mode 100644 index 0000000..a2347c7 --- /dev/null +++ b/trainer/src/components/workout-history/workout-history.html @@ -0,0 +1,41 @@ +
+

Workout History

+
+ +
+ + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
NoStartedEndedLast ExerciseExercises DoneCompleted
{{i+1}}{{historyItem.startedOn | date:'short'}}{{historyItem.endedOn | date:'short'}}{{historyItem.lastExercise}}{{historyItem.exercisesDone}}{{historyItem.completed ? "Yes" : "No"}}
No Workout History Found.
+
+
+ +
+
diff --git a/trainer/src/components/workout-history/workout-history.module.ts b/trainer/src/components/workout-history/workout-history.module.ts new file mode 100644 index 0000000..bb0227a --- /dev/null +++ b/trainer/src/components/workout-history/workout-history.module.ts @@ -0,0 +1,13 @@ +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { WorkoutHistoryComponent } from './workout-history.component'; +import {SharedModule} from '../shared/shared.module'; + +@NgModule({ + imports: [ + BrowserModule, + SharedModule], + declarations: [WorkoutHistoryComponent], + exports: [WorkoutHistoryComponent], +}) +export class WorkoutHistoryModule { } \ No newline at end of file diff --git a/trainer/src/components/workout-runner/exercise-description.ts b/trainer/src/components/workout-runner/exercise-description/exercise-description.component.ts similarity index 58% rename from trainer/src/components/workout-runner/exercise-description.ts rename to trainer/src/components/workout-runner/exercise-description/exercise-description.component.ts index eb6648c..c6e1084 100644 --- a/trainer/src/components/workout-runner/exercise-description.ts +++ b/trainer/src/components/workout-runner/exercise-description/exercise-description.component.ts @@ -1,10 +1,10 @@ -import {Component, Input} from 'angular2/core'; +import {Component, Input} from '@angular/core'; @Component({ selector: 'exercise-description', - templateUrl: '/src/components/workout-runner/exercise-description.tpl.html', + templateUrl: '/src/components/workout-runner/exercise-description/exercise-description.html', }) -export class ExerciseDescription { +export class ExerciseDescriptionComponent { @Input() description: string; @Input() steps: string; } diff --git a/trainer/src/components/workout-runner/exercise-description.tpl.html b/trainer/src/components/workout-runner/exercise-description/exercise-description.html similarity index 100% rename from trainer/src/components/workout-runner/exercise-description.tpl.html rename to trainer/src/components/workout-runner/exercise-description/exercise-description.html diff --git a/trainer/src/components/workout-runner/finish.ts b/trainer/src/components/workout-runner/finish.ts deleted file mode 100644 index 38d2178..0000000 --- a/trainer/src/components/workout-runner/finish.ts +++ /dev/null @@ -1,10 +0,0 @@ -import {Component, Input} from 'angular2/core'; -import {ROUTER_DIRECTIVES} from 'angular2/router'; - -@Component({ - selector: 'finish', - templateUrl: '/src/components/workout-runner/finish.tpl.html', - directives: [ROUTER_DIRECTIVES] -}) -export class Finish { -} diff --git a/trainer/src/components/workout-runner/pipes.ts b/trainer/src/components/workout-runner/seconds-to-time.pipe.ts similarity index 72% rename from trainer/src/components/workout-runner/pipes.ts rename to trainer/src/components/workout-runner/seconds-to-time.pipe.ts index 10c1261..5800663 100644 --- a/trainer/src/components/workout-runner/pipes.ts +++ b/trainer/src/components/workout-runner/seconds-to-time.pipe.ts @@ -1,10 +1,10 @@ -import {Pipe} from 'angular2/core'; +import {Pipe, PipeTransform} from '@angular/core'; @Pipe({ name: 'secondsToTime' }) -export class SecondsToTime { - transform(value: number, args: any[]): any { +export class SecondsToTimePipe implements PipeTransform { + transform(value: number): any { if (!isNaN(value)) { var hours = Math.floor(value / 3600); var minutes = Math.floor((value - (hours * 3600)) / 60); @@ -16,4 +16,4 @@ export class SecondsToTime { } return; } -} +} \ No newline at end of file diff --git a/trainer/src/components/workout-runner/start.ts b/trainer/src/components/workout-runner/start.ts deleted file mode 100644 index bb08d83..0000000 --- a/trainer/src/components/workout-runner/start.ts +++ /dev/null @@ -1,10 +0,0 @@ -import {Component, Input} from 'angular2/core'; -import {ROUTER_DIRECTIVES} from 'angular2/router'; - -@Component({ - selector: 'start', - templateUrl: '/src/components/workout-runner/start.tpl.html', - directives: [ROUTER_DIRECTIVES] -}) -export class Start { -} diff --git a/trainer/src/components/workout-runner/video-player.tpl.html b/trainer/src/components/workout-runner/video-player.tpl.html deleted file mode 100644 index 95c17f5..0000000 --- a/trainer/src/components/workout-runner/video-player.tpl.html +++ /dev/null @@ -1,10 +0,0 @@ -
-
-

Videos

-
-
-
- -
-
-
diff --git a/trainer/src/components/workout-runner/video-player.ts b/trainer/src/components/workout-runner/video-player.ts deleted file mode 100644 index 22bd2db..0000000 --- a/trainer/src/components/workout-runner/video-player.ts +++ /dev/null @@ -1,9 +0,0 @@ -import {Component, Input} from 'angular2/core'; - -@Component({ - selector: 'video-player', - templateUrl: '/src/components/workout-runner/video-player.tpl.html' -}) -export class VideoPlayer { - @Input() videos: Array; -} diff --git a/trainer/src/components/workout-runner/video-player/video-dialog.component.ts b/trainer/src/components/workout-runner/video-player/video-dialog.component.ts new file mode 100644 index 0000000..c49d106 --- /dev/null +++ b/trainer/src/components/workout-runner/video-player/video-dialog.component.ts @@ -0,0 +1,40 @@ +import {Component, OnInit} from '@angular/core'; +import {DialogRef, ModalComponent} from 'angular2-modal'; +import {BSModalContext} from 'angular2-modal/plugins/bootstrap' +import {DomSanitizer, SafeResourceUrl} from '@angular/platform-browser'; + +export class VideoDialogContext extends BSModalContext { + constructor(public videoId: string) { + super(); + this.size = "sm"; + } +} + +// Custom dialog class for showing view in a popup. +@Component({ + selector: 'video-dialog', + template: ` + + `, +}) +export class VideoDialogComponent implements ModalComponent, OnInit { + context: VideoDialogContext; + videoId: SafeResourceUrl; + private youtubeUrlPrefix = '//www.youtube.com/embed/'; + + constructor(public dialog: DialogRef, private sanitizer: DomSanitizer) { } + + ngOnInit() { + this.videoId = this.sanitizer.bypassSecurityTrustResourceUrl(this.youtubeUrlPrefix + this.dialog.context.videoId); + } + + ok() { + this.dialog.close(); + } +} diff --git a/trainer/src/components/workout-runner/video-player/video-player.component.ts b/trainer/src/components/workout-runner/video-player/video-player.component.ts new file mode 100644 index 0000000..f6d5e74 --- /dev/null +++ b/trainer/src/components/workout-runner/video-player/video-player.component.ts @@ -0,0 +1,26 @@ +import {Component, Input, Output, EventEmitter} from '@angular/core'; +import {SafeResourceUrl} from '@angular/platform-browser'; +import { Modal } from 'angular2-modal/plugins/bootstrap'; +import { overlayConfigFactory } from 'angular2-modal' +import {VideoDialogComponent, VideoDialogContext} from './video-dialog.component'; + +@Component({ + selector: 'video-player', + templateUrl: '/src/components/workout-runner/video-player/video-player.html' +}) +export class VideoPlayerComponent { + @Input() videos: Array; + @Output() playbackStarted: EventEmitter = new EventEmitter(); + @Output() playbackEnded: EventEmitter = new EventEmitter(); + + constructor(private modal: Modal) { } + + playVideo(videoId: string) { + this.playbackStarted.emit(null); + + var dialog = this.modal.open(VideoDialogComponent, overlayConfigFactory(new VideoDialogContext(videoId))); + dialog + .then((d) => d.result) + .then(() => { this.playbackEnded.emit(null); }, (error) => { this.playbackEnded.emit(null); }); + }; +} \ No newline at end of file diff --git a/trainer/src/components/workout-runner/video-player/video-player.html b/trainer/src/components/workout-runner/video-player/video-player.html new file mode 100644 index 0000000..2cb9dbe --- /dev/null +++ b/trainer/src/components/workout-runner/video-player/video-player.html @@ -0,0 +1,15 @@ +
+
+

Videos

+
+
+
+
+
+ +
+ +
+
+
+
diff --git a/trainer/src/components/workout-runner/workout-audio.ts b/trainer/src/components/workout-runner/workout-audio.ts deleted file mode 100644 index 0ca0133..0000000 --- a/trainer/src/components/workout-runner/workout-audio.ts +++ /dev/null @@ -1,78 +0,0 @@ -import {Component, ContentChild, ViewChild, ViewChildren, ElementRef, QueryList, Host, Injector} from 'angular2/core'; -import {MyAudio} from './my-audio' -import {WorkoutRunner} from './workout-runner' -import {WorkoutPlan, ExercisePlan} from './model'; - -@Component({ - selector: 'workout-audio', - templateUrl: '/src/components/workout-runner/workout-audio.tpl.html', - directives: [MyAudio] -}) -export class WorkoutAudio { - @ViewChild('ticks') ticks: MyAudio; - @ViewChild('nextUp') nextUp: MyAudio; - @ViewChild('nextUpExercise') nextUpExercise: MyAudio; - @ViewChild('halfway') halfway: MyAudio; - @ViewChild('aboutToComplete') aboutToComplete: MyAudio; - private runner: WorkoutRunner; - private nameSounds: Array; - private nextupSound: string; - - constructor(public injector: Injector) { - this.runner = this.injector.get(WorkoutRunner); - - this.runner.exercisePaused.subscribe((exercise: ExercisePlan) => this.stop()); - this.runner.workoutComplete.subscribe((exercise: ExercisePlan) => this.stop()); - this.runner.exerciseResumed.subscribe((exercise: ExercisePlan) => this.resume()); - this.runner.exerciseProgress.subscribe((progress: any) => this.onExerciseProgress(progress)); - this.runner.exerciseChanged.subscribe((state: any) => this.onExerciseChanged(state)); - - } - - /*afterViewInit() { - console.log(this.ticks); - }*/ - - stop() { - this.ticks.stop(); - this.nextUp.stop(); - this.halfway.stop(); - this.aboutToComplete.stop(); - this.nextUpExercise.stop(); - } - - resume() { - this.ticks.start(); - console.log(this.nextUp.currentTime()); - console.log(this.nextUpExercise.currentTime()); - console.log(this.halfway.currentTime()); - console.log(this.aboutToComplete.currentTime()); - - if (this.nextUp.currentTime() > 0 && !this.nextUp.playbackComplete()) this.nextUp.start(); - else if (this.nextUpExercise.currentTime() > 0 && !this.nextUpExercise.playbackComplete()) this.nextUpExercise.start(); - else if (this.halfway.currentTime() > 0 && !this.halfway.playbackComplete()) this.halfway.start(); - else if (this.aboutToComplete.currentTime() > 0 && !this.aboutToComplete.playbackComplete()) this.aboutToComplete.start(); - } - - private onExerciseProgress(progress: any) { - if (progress.runningFor == Math.floor(progress.exercise.duration / 2) - && progress.exercise.exercise.name != "rest") { - this.halfway.start(); - } - else if (progress.timeRemaining == 3) { - this.aboutToComplete.start(); - } - } - - private onExerciseChanged(state: any) { - if (state.current.exercise.name == "rest") { - this.nextupSound = state.next.exercise.nameSound; - setTimeout(() => this.nextUp.start(), 2000); - setTimeout(() => this.nextUpExercise.start(), 3000); - } - } - - private onWorkoutStarted(workout: WorkoutPlan) { - - } -} diff --git a/trainer/src/components/workout-runner/my-audio.ts b/trainer/src/components/workout-runner/workout-audio/my-audio.directive.ts similarity index 63% rename from trainer/src/components/workout-runner/my-audio.ts rename to trainer/src/components/workout-runner/workout-audio/my-audio.directive.ts index 4ca97da..cef107d 100644 --- a/trainer/src/components/workout-runner/my-audio.ts +++ b/trainer/src/components/workout-runner/workout-audio/my-audio.directive.ts @@ -1,10 +1,10 @@ -import {Directive, Input, ElementRef} from 'angular2/core'; +import {Directive, ElementRef} from '@angular/core'; @Directive({ selector: 'audio', exportAs: 'MyAudio' }) -export class MyAudio { +export class MyAudioDirective { private audioPlayer: HTMLAudioElement; constructor(element: ElementRef) { this.audioPlayer = element.nativeElement; @@ -16,14 +16,14 @@ export class MyAudio { start() { this.audioPlayer.play(); } - currentTime(): number { + get currentTime(): number { return this.audioPlayer.currentTime; } - duration(): number { + get duration(): number { return this.audioPlayer.duration; } - playbackComplete() { - return this.duration() == this.currentTime(); + get playbackComplete() { + return this.duration == this.currentTime; } } diff --git a/trainer/src/components/workout-runner/workout-audio/workout-audio.component.ts b/trainer/src/components/workout-runner/workout-audio/workout-audio.component.ts new file mode 100644 index 0000000..db7902e --- /dev/null +++ b/trainer/src/components/workout-runner/workout-audio/workout-audio.component.ts @@ -0,0 +1,50 @@ +import {Component, ViewChild, Inject, forwardRef} from '@angular/core'; +import {MyAudioDirective} from './my-audio.directive' +import {WorkoutPlan, ExercisePlan, ExerciseProgressEvent, ExerciseChangedEvent} from '../../../services/model'; + +@Component({ + selector: 'workout-audio', + templateUrl: '/src/components/workout-runner/workout-audio/workout-audio.html' +}) +export class WorkoutAudioComponent { + @ViewChild('ticks') private ticks: MyAudioDirective; + @ViewChild('nextUp') private nextUp: MyAudioDirective; + @ViewChild('nextUpExercise') private nextUpExercise: MyAudioDirective; + @ViewChild('halfway') private halfway: MyAudioDirective; + @ViewChild('aboutToComplete') private aboutToComplete: MyAudioDirective; + private nextupSound: string; + + stop() { + this.ticks.stop(); + this.nextUp.stop(); + this.halfway.stop(); + this.aboutToComplete.stop(); + this.nextUpExercise.stop(); + } + + resume() { + this.ticks.start(); + if (this.nextUp.currentTime > 0 && !this.nextUp.playbackComplete) this.nextUp.start(); + else if (this.nextUpExercise.currentTime > 0 && !this.nextUpExercise.playbackComplete) this.nextUpExercise.start(); + else if (this.halfway.currentTime > 0 && !this.halfway.playbackComplete) this.halfway.start(); + else if (this.aboutToComplete.currentTime > 0 && !this.aboutToComplete.playbackComplete) this.aboutToComplete.start(); + } + + onExerciseProgress(progress: ExerciseProgressEvent) { + if (progress.runningFor == Math.floor(progress.exercise.duration / 2) + && progress.exercise.exercise.name != "rest") { + this.halfway.start(); + } + else if (progress.timeRemaining == 3) { + this.aboutToComplete.start(); + } + } + + onExerciseChanged(state: ExerciseChangedEvent) { + if (state.current.exercise.name == "rest") { + this.nextupSound = state.next.exercise.nameSound; + setTimeout(() => this.nextUp.start(), 2000); + setTimeout(() => this.nextUpExercise.start(), 3000); + } + } +} diff --git a/trainer/src/components/workout-runner/workout-audio.tpl.html b/trainer/src/components/workout-runner/workout-audio/workout-audio.html similarity index 78% rename from trainer/src/components/workout-runner/workout-audio.tpl.html rename to trainer/src/components/workout-runner/workout-audio/workout-audio.html index a92bd25..6758dde 100644 --- a/trainer/src/components/workout-runner/workout-audio.tpl.html +++ b/trainer/src/components/workout-runner/workout-audio/workout-audio.html @@ -1,4 +1,4 @@ - + diff --git a/trainer/src/components/workout-runner/workout-audio/workout-audio0.component.ts b/trainer/src/components/workout-runner/workout-audio/workout-audio0.component.ts new file mode 100644 index 0000000..c828f12 --- /dev/null +++ b/trainer/src/components/workout-runner/workout-audio/workout-audio0.component.ts @@ -0,0 +1,66 @@ +import {Component, ViewChild, Inject, forwardRef} from '@angular/core'; +import {MyAudioDirective} from './my-audio.directive' +import {WorkoutRunnerComponent} from '../workout-runner.component' +import {WorkoutPlan, ExercisePlan, ExerciseProgressEvent, ExerciseChangedEvent} from '../../../services/model'; + +@Component({ + selector: 'workout-audio', + templateUrl: '/src/components/workout-runner/workout-audio/workout-audio.html' +}) +export class WorkoutAudioComponent { + @ViewChild('ticks') private ticks: MyAudioDirective; + @ViewChild('nextUp') private nextUp: MyAudioDirective; + @ViewChild('nextUpExercise') private nextUpExercise: MyAudioDirective; + @ViewChild('halfway') private halfway: MyAudioDirective; + @ViewChild('aboutToComplete') private aboutToComplete: MyAudioDirective; + private nameSounds: Array; + private nextupSound: string; + private subscriptions: Array; + + constructor( @Inject(forwardRef(() => WorkoutRunnerComponent)) private runner: WorkoutRunnerComponent) { + this.subscriptions = [ + this.runner.exercisePaused.subscribe((exercise: ExercisePlan) => this.stop()), + this.runner.workoutComplete.subscribe((exercise: ExercisePlan) => this.stop()), + this.runner.exerciseResumed.subscribe((exercise: ExercisePlan) => this.resume()), + this.runner.exerciseProgress.subscribe((progress: ExerciseProgressEvent) => this.onExerciseProgress(progress)), + this.runner.exerciseChanged.subscribe((state: ExerciseChangedEvent) => this.onExerciseChanged(state))] + } + + stop() { + this.ticks.stop(); + this.nextUp.stop(); + this.halfway.stop(); + this.aboutToComplete.stop(); + this.nextUpExercise.stop(); + } + + resume() { + this.ticks.start(); + if (this.nextUp.currentTime > 0 && !this.nextUp.playbackComplete) this.nextUp.start(); + else if (this.nextUpExercise.currentTime > 0 && !this.nextUpExercise.playbackComplete) this.nextUpExercise.start(); + else if (this.halfway.currentTime > 0 && !this.halfway.playbackComplete) this.halfway.start(); + else if (this.aboutToComplete.currentTime > 0 && !this.aboutToComplete.playbackComplete) this.aboutToComplete.start(); + } + + private onExerciseProgress(exercise: any) { + if (exercise.runningFor == Math.floor(exercise.exercise.duration / 2) + && exercise.exercise.exercise.name != "rest") { + this.halfway.start(); + } + else if (exercise.timeRemaining == 3) { + this.aboutToComplete.start(); + } + } + + private onExerciseChanged(state: any) { + if (state.current.exercise.name == "rest") { + this.nextupSound = state.next.exercise.nameSound; + setTimeout(() => this.nextUp.start(), 2000); + setTimeout(() => this.nextUpExercise.start(), 3000); + } + } + + ngOnDestroy() { + this.subscriptions.forEach((s) => s.unsubscribe()); + } +} diff --git a/trainer/src/components/workout-runner/workout-container/workout-container.component.ts b/trainer/src/components/workout-runner/workout-container/workout-container.component.ts new file mode 100644 index 0000000..02c9db5 --- /dev/null +++ b/trainer/src/components/workout-runner/workout-container/workout-container.component.ts @@ -0,0 +1,8 @@ +import {Component, Input} from '@angular/core'; +import {WorkoutAudioComponent} from '../workout-audio/workout-audio.component'; +import {WorkoutRunnerComponent} from '../workout-runner.component'; +@Component({ + selector: 'workout-container', + templateUrl: '/src/components/workout-runner/workout-container/workout-container.html' +}) +export class WorkoutContainerCompnent { } diff --git a/trainer/src/components/workout-runner/workout-container/workout-container.html b/trainer/src/components/workout-runner/workout-container/workout-container.html new file mode 100644 index 0000000..85beb34 --- /dev/null +++ b/trainer/src/components/workout-runner/workout-container/workout-container.html @@ -0,0 +1,8 @@ + + + diff --git a/trainer/src/components/workout-runner/workout-runner.ts b/trainer/src/components/workout-runner/workout-runner.component.ts similarity index 83% rename from trainer/src/components/workout-runner/workout-runner.ts rename to trainer/src/components/workout-runner/workout-runner.component.ts index a4fc6ab..2c3b94a 100644 --- a/trainer/src/components/workout-runner/workout-runner.ts +++ b/trainer/src/components/workout-runner/workout-runner.component.ts @@ -1,18 +1,13 @@ -import {Component, ViewChild, EventEmitter, Output, OnInit} from 'angular2/core'; -import {WorkoutPlan, ExercisePlan, Exercise} from './model'; -import {ExerciseDescription} from './exercise-description'; -import {VideoPlayer} from './video-player'; -import {WorkoutAudio} from './workout-audio'; -import {SecondsToTime} from './pipes'; -import {Router} from 'angular2/router'; +import {Component, ViewChild, EventEmitter, Output, OnInit, OnDestroy} from '@angular/core'; +import {WorkoutPlan, ExercisePlan, Exercise, ExerciseProgressEvent, ExerciseChangedEvent} from '../../services/model'; +import {Router} from '@angular/router'; +import {WorkoutHistoryTracker} from '../../services/workout-history-tracker'; @Component({ selector: 'workout-runner', - templateUrl: '/src/components/workout-runner/workout-runner.tpl.html', - directives: [ExerciseDescription, VideoPlayer, WorkoutAudio], - pipes: [SecondsToTime] + templateUrl: '/src/components/workout-runner/workout-runner.html' }) -export class WorkoutRunner implements OnInit { +export class WorkoutRunnerComponent implements OnInit, OnDestroy { workoutPlan: WorkoutPlan; workoutTimeRemaining: number; restExercise: ExercisePlan; @@ -21,16 +16,15 @@ export class WorkoutRunner implements OnInit { exerciseRunningDuration: number; exerciseTrackingInterval: number; workoutPaused: boolean; - @ViewChild(WorkoutAudio) workoutAudioPlayer: WorkoutAudio; @Output() exercisePaused: EventEmitter = new EventEmitter(); @Output() exerciseResumed: EventEmitter = new EventEmitter(); - @Output() exerciseProgress: EventEmitter = new EventEmitter(); - @Output() exerciseChanged: EventEmitter = new EventEmitter(); + @Output() exerciseProgress: EventEmitter = new EventEmitter(); + @Output() exerciseChanged: EventEmitter = new EventEmitter(); @Output() workoutStarted: EventEmitter = new EventEmitter(); @Output() workoutComplete: EventEmitter = new EventEmitter(); - - constructor(private _router: Router) { + constructor(private router: Router, + private tracker: WorkoutHistoryTracker) { this.workoutPlan = this.buildWorkout(); this.restExercise = new ExercisePlan(new Exercise("rest", "Relax!", "Relax a bit", "rest.png"), this.workoutPlan.restBetweenExercise); } @@ -39,28 +33,23 @@ export class WorkoutRunner implements OnInit { } start() { + this.tracker.startTracking(); this.workoutTimeRemaining = this.workoutPlan.totalWorkoutDuration(); this.currentExerciseIndex = 0; this.startExercise(this.workoutPlan.exercises[this.currentExerciseIndex]); - this.workoutStarted.next(this.workoutPlan); - /*let intervalId = setInterval(() => { - --this.workoutTimeRemaining; - if (this.workoutTimeRemaining == 0) clearInterval(intervalId); - }, 1000, this.workoutTimeRemaining);*/ + this.workoutStarted.emit(this.workoutPlan); } pause() { clearInterval(this.exerciseTrackingInterval); this.workoutPaused = true; - this.workoutAudioPlayer.stop(); - this.exercisePaused.next(this.currentExerciseIndex); + this.exercisePaused.emit(this.currentExerciseIndex); } resume() { this.startExerciseTimeTracking(); this.workoutPaused = false; - this.workoutAudioPlayer.resume(); - this.exerciseResumed.next(this.currentExerciseIndex); + this.exerciseResumed.emit(this.currentExerciseIndex); } pauseResumeToggle() { @@ -71,7 +60,7 @@ export class WorkoutRunner implements OnInit { this.pause(); } } - onKeyPressed = function(event: KeyboardEvent) { + onKeyPressed = function (event: KeyboardEvent) { if (event.which == 80 || event.which == 112) { // 'p' or 'P' key to toggle pause and resume. this.pauseResumeToggle(); } @@ -80,21 +69,6 @@ export class WorkoutRunner implements OnInit { this.currentExercise = exercisePlan; this.exerciseRunningDuration = 0; this.startExerciseTimeTracking(); - /*let intervalId = setInterval(() => { - if (this.exerciseRunningDuration >= this.currentExercise.duration) { - clearInterval(intervalId); - let next: ExercisePlan = this.getNextExercise(); - if (next) { - this.startExercise(next); - } - else { - console.log("Workout complete!"); - } - } - else { - this.exerciseRunningDuration++; - } - }, 1000, this.currentExercise.duration);*/ } getNextExercise(): ExercisePlan { @@ -112,31 +86,40 @@ export class WorkoutRunner implements OnInit { this.exerciseTrackingInterval = window.setInterval(() => { if (this.exerciseRunningDuration >= this.currentExercise.duration) { clearInterval(this.exerciseTrackingInterval); + if (this.currentExercise !== this.restExercise) { + this.tracker.exerciseComplete(this.workoutPlan.exercises[this.currentExerciseIndex]); + } let next: ExercisePlan = this.getNextExercise(); if (next) { if (next !== this.restExercise) { this.currentExerciseIndex++; } this.startExercise(next); - this.exerciseChanged.next({ current: next, next: this.getNextExercise() }); + this.exerciseChanged.emit(new ExerciseChangedEvent(next, this.getNextExercise())); } else { - this.workoutComplete.next(this.workoutPlan); - this._router.navigate( ['Finish'] ); + this.tracker.endTracking(true); + this.workoutComplete.emit(this.workoutPlan); + this.router.navigate(['/finish']); } return; } ++this.exerciseRunningDuration; --this.workoutTimeRemaining; - this.exerciseProgress.next({ - exercise: this.currentExercise, - runningFor: this.exerciseRunningDuration, - timeRemaining: this.currentExercise.duration - this.exerciseRunningDuration, - workoutTimeRemaining: this.workoutTimeRemaining - }); + this.exerciseProgress.emit(new ExerciseProgressEvent( + this.currentExercise, + this.exerciseRunningDuration, + this.currentExercise.duration - this.exerciseRunningDuration, + this.workoutTimeRemaining + )); }, 1000); } + ngOnDestroy() { + this.tracker.endTracking(false); + if (this.exerciseTrackingInterval) clearInterval(this.exerciseTrackingInterval); + } + buildWorkout(): WorkoutPlan { let workout = new WorkoutPlan("7MinWorkout", "7 Minute Workout", 10, []); workout.exercises.push( diff --git a/trainer/src/components/workout-runner/workout-runner.tpl.html b/trainer/src/components/workout-runner/workout-runner.html similarity index 92% rename from trainer/src/components/workout-runner/workout-runner.tpl.html rename to trainer/src/components/workout-runner/workout-runner.html index bdb035b..b4aaf0a 100644 --- a/trainer/src/components/workout-runner/workout-runner.tpl.html +++ b/trainer/src/components/workout-runner/workout-runner.html @@ -29,7 +29,6 @@

Ne
- +
- diff --git a/trainer/src/components/workout-runner/workout-runner.module.ts b/trainer/src/components/workout-runner/workout-runner.module.ts new file mode 100644 index 0000000..4b0da6d --- /dev/null +++ b/trainer/src/components/workout-runner/workout-runner.module.ts @@ -0,0 +1,28 @@ +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; + +import { WorkoutRunnerComponent } from './workout-runner.component'; +import {WorkoutContainerCompnent} from './workout-container/workout-container.component'; + +import {ExerciseDescriptionComponent} from './exercise-description/exercise-description.component'; +import {VideoPlayerComponent} from './video-player/video-player.component'; +import {VideoDialogComponent} from './video-player/video-dialog.component'; +import {SecondsToTimePipe} from './seconds-to-time.pipe'; +import {WorkoutAudioComponent} from './workout-audio/workout-audio.component'; +import {MyAudioDirective} from './workout-audio/my-audio.directive'; + +@NgModule({ + imports: [BrowserModule], + declarations: [ + WorkoutRunnerComponent, + ExerciseDescriptionComponent, + VideoPlayerComponent, + VideoDialogComponent, + SecondsToTimePipe, + WorkoutContainerCompnent, + WorkoutAudioComponent, + MyAudioDirective], + exports: [WorkoutContainerCompnent], + entryComponents:[VideoDialogComponent] +}) +export class WorkoutRunnerModule { } \ No newline at end of file diff --git a/trainer/src/services/local-storage.ts b/trainer/src/services/local-storage.ts new file mode 100644 index 0000000..e4b7d89 --- /dev/null +++ b/trainer/src/services/local-storage.ts @@ -0,0 +1,12 @@ +export class LocalStorage { + getItem(key: string): T { + if (localStorage[key]) { + return JSON.parse(localStorage[key]); + } + return null; + } + + setItem(key: string, item: any) { + localStorage[key] = JSON.stringify(item); + } +} diff --git a/trainer/src/components/workout-runner/model.ts b/trainer/src/services/model.ts similarity index 53% rename from trainer/src/components/workout-runner/model.ts rename to trainer/src/services/model.ts index d48a705..dddc8c6 100644 --- a/trainer/src/components/workout-runner/model.ts +++ b/trainer/src/services/model.ts @@ -10,14 +10,14 @@ export class WorkoutPlan { totalWorkoutDuration(): number { if (!this.exercises) return 0; - let total = this.exercises.map((e) => e.duration).reduce((previous, current) => previous + current); + let total = this.exercises.map((e) => e.duration).reduce((previous, current) => parseInt(previous) + parseInt(current)); - return (this.restBetweenExercise ? this.restBetweenExercise : 0) * (this.exercises.length - 1) + total; + return ((this.restBetweenExercise ? this.restBetweenExercise : 0) * (this.exercises.length - 1)) + total; } } export class ExercisePlan { - constructor(public exercise: Exercise, public duration: number) { + constructor(public exercise: Exercise, public duration: any) { } } @@ -32,3 +32,18 @@ export class Exercise { public procedure?: string, public videos?: Array) { } } + +export class ExerciseProgressEvent { + constructor( + public exercise: ExercisePlan, + public runningFor: number, + public timeRemaining: number, + public workoutTimeRemaining: number) { } +} + +export class ExerciseChangedEvent { + constructor( + public current: ExercisePlan, + public next: ExercisePlan + ) { } +} diff --git a/trainer/src/services/services.module.ts b/trainer/src/services/services.module.ts new file mode 100644 index 0000000..bacfd32 --- /dev/null +++ b/trainer/src/services/services.module.ts @@ -0,0 +1,14 @@ +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; + +import {LocalStorage} from './local-storage'; +import {WorkoutHistoryTracker} from './workout-history-tracker'; + +@NgModule({ + imports: [], + declarations: [], + providers: [ + LocalStorage, + WorkoutHistoryTracker], +}) +export class ServicesModule { } \ No newline at end of file diff --git a/trainer/src/services/workout-history-tracker.ts b/trainer/src/services/workout-history-tracker.ts new file mode 100644 index 0000000..b61a988 --- /dev/null +++ b/trainer/src/services/workout-history-tracker.ts @@ -0,0 +1,61 @@ +import {ExercisePlan} from './model'; +import {LocalStorage} from './local-storage'; +import {Injectable} from '@angular/core'; + +@Injectable() +export class WorkoutHistoryTracker { + private maxHistoryItems: number = 20; //We only track for last 20 exercise + private currentWorkoutLog: WorkoutLogEntry = null; + private workoutHistory: Array = []; + private workoutTracked: boolean; + private storageKey: string = "workouts"; + + constructor(private storage: LocalStorage) { + this.workoutHistory = (storage.getItem>(this.storageKey) || []) + .map((item: WorkoutLogEntry) => { + item.startedOn = new Date(item.startedOn.toString()); + item.endedOn = item.endedOn == null ? null : new Date(item.endedOn.toString()); + return item; + }); + } + + get tracking(): boolean { + return this.workoutTracked; + } + + startTracking() { + this.workoutTracked = true; + this.currentWorkoutLog = new WorkoutLogEntry(new Date()); + if (this.workoutHistory.length >= this.maxHistoryItems) { + this.workoutHistory.shift(); + } + this.workoutHistory.push(this.currentWorkoutLog); + this.storage.setItem(this.storageKey, this.workoutHistory); + } + + exerciseComplete(exercise: ExercisePlan) { + this.currentWorkoutLog.lastExercise = exercise.exercise.title; + ++this.currentWorkoutLog.exercisesDone; + this.storage.setItem(this.storageKey, this.workoutHistory); + } + + endTracking(completed: boolean) { + this.currentWorkoutLog.completed = completed; + this.currentWorkoutLog.endedOn = new Date(); + this.currentWorkoutLog = null; + this.workoutTracked = false; + this.storage.setItem(this.storageKey, this.workoutHistory); + }; + + getHistory(): Array { + return this.workoutHistory; + } +} +export class WorkoutLogEntry { + constructor( + public startedOn: Date, + public completed: boolean = false, + public exercisesDone: number = 0, + public lastExercise?: string, + public endedOn?: Date) { } +} diff --git a/trainer/src/tsconfig.json b/trainer/src/tsconfig.json index 33711e2..b3f6106 100644 --- a/trainer/src/tsconfig.json +++ b/trainer/src/tsconfig.json @@ -20,16 +20,6 @@ "!node_modules/**" ], "files": [ - "bootstrap.ts", - "components/app/app.ts", - "components/workout-runner/exercise-description.ts", - "components/workout-runner/finish.ts", - "components/workout-runner/model.ts", - "components/workout-runner/my-audio.ts", - "components/workout-runner/pipes.ts", - "components/workout-runner/start.ts", - "components/workout-runner/video-player.ts", - "components/workout-runner/workout-audio.ts", - "components/workout-runner/workout-runner.ts" + "bootstrap.ts" ] } diff --git a/trainer/static/css/app.css b/trainer/static/css/app.css index d2a0954..64a7251 100644 --- a/trainer/static/css/app.css +++ b/trainer/static/css/app.css @@ -51,9 +51,12 @@ input[type="checkbox"].input-validation-error { background-color: #4eaded; border-color: #4eaded; } -.navbar-default .second-top-nav{ +.second-top-nav{ background-color: #0088dc !important; + color: white; height:43px; + position:fixed !important; + top:70px !important; } .navbar-default .second-top-nav a { color: white !important; @@ -104,19 +107,20 @@ input[type="checkbox"].input-validation-error { .text-center{ color:#4eaded; } -#start{ + +.action{ text-align:center; margin-top:25px; } -#start a { +.action a { text-decoration: none; font-size: 54px; color:#4eaded; } -#start a:hover{ - color:#4eaded; +.action a:hover{ + color:#005E9E; } -#start a .glyphicon { +.action a .glyphicon { top:8px; } /*Styles for start page - end*/ diff --git a/trainer/systemjs.config.js b/trainer/systemjs.config.js new file mode 100644 index 0000000..765bb23 --- /dev/null +++ b/trainer/systemjs.config.js @@ -0,0 +1,60 @@ +(function (global) { + + // map tells the System loader where to look for things + var map = { + 'app': 'dist', // 'dist', + 'rxjs': 'node_modules/rxjs', + '@angular': 'node_modules/@angular', + 'angular2-modal': 'node_modules/angular2-modal', + 'angular2-modal/platform-browser': 'node_modules/angular2-modal/platform-browser', + 'angular2-modal/plugins/bootstrap': 'node_modules/angular2-modal/plugins/bootstrap' + }; + + // packages tells the System loader how to load when no filename and/or no extension + var packages = { + 'app': { main: 'bootstrap.js', defaultExtension: 'js' }, + 'rxjs': { defaultExtension: 'js' }, + 'angular2-modal': {main: 'index.js', defaultExtension: 'js'}, + 'angular2-modal/platform-browser': {main: 'index.js', defaultExtension: 'js'}, + 'angular2-modal/plugins/bootstrap': {main: 'index.js', defaultExtension: 'js'}, + }; + + var ngPackageNames = [ + 'common', + 'compiler', + 'core', + 'http', + 'platform-browser', + 'platform-browser-dynamic', + 'router', + 'testing' + ]; + + // Individual files (~300 requests): + function packIndex(pkgName) { + packages['@angular/' + pkgName] = { main: 'index.js', defaultExtension: 'js' }; + } + + // add package entries for angular packages in the form '@angular/common': { main: 'index.js', defaultExtension: 'js' } + // Bundled (~40 requests): + function packUmd(pkgName) { + packages['@angular/' + pkgName] = { main: '/bundles/' + pkgName + '.umd.js', defaultExtension: 'js' }; + } + + // Most environments should use UMD; some (Karma) need the individual index files + var setPackageConfig = System.packageWithIndex ? packIndex : packUmd; + + // Add package entries for angular packages + ngPackageNames.forEach(setPackageConfig); + + var config = { + map: map, + packages: packages + } + + // filterSystemConfig - index.html's chance to modify config before we register it. + if (global.filterSystemConfig) { global.filterSystemConfig(config); } + + System.config(config); + +})(this); \ No newline at end of file