diff --git a/trainer/src/app/app.component.html b/trainer/src/app/app.component.html index 89be770..ec4ccb1 100644 --- a/trainer/src/app/app.component.html +++ b/trainer/src/app/app.component.html @@ -1,4 +1,6 @@ -

Welcome to, {{title}}!

\ No newline at end of file +
+ +
\ No newline at end of file diff --git a/trainer/src/app/app.module.ts b/trainer/src/app/app.module.ts index 926975a..6fae147 100644 --- a/trainer/src/app/app.module.ts +++ b/trainer/src/app/app.module.ts @@ -3,6 +3,7 @@ import { NgModule } from '@angular/core'; import { AppComponent } from './app.component'; +import { WorkoutRunnerModule } from './workout-runner/workout-runner.module'; @NgModule({ @@ -10,7 +11,8 @@ import { AppComponent } from './app.component'; AppComponent ], imports: [ - BrowserModule + BrowserModule, + WorkoutRunnerModule ], providers: [], bootstrap: [AppComponent] diff --git a/trainer/src/app/workout-runner/exercise-description/exercise-description.component.html b/trainer/src/app/workout-runner/exercise-description/exercise-description.component.html new file mode 100644 index 0000000..f563adb --- /dev/null +++ b/trainer/src/app/workout-runner/exercise-description/exercise-description.component.html @@ -0,0 +1,20 @@ +
+
+

+ Description +

+
+
{{description}}
+
+
+
+
+

+ Steps +

+
+
+
+
+
+
diff --git a/trainer/src/app/workout-runner/exercise-description/exercise-description.component.spec.ts b/trainer/src/app/workout-runner/exercise-description/exercise-description.component.spec.ts new file mode 100644 index 0000000..6113f51 --- /dev/null +++ b/trainer/src/app/workout-runner/exercise-description/exercise-description.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ExerciseDescriptionComponent } from './exercise-description.component'; + +describe('ExerciseDescriptionComponent', () => { + let component: ExerciseDescriptionComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ ExerciseDescriptionComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ExerciseDescriptionComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/trainer/src/app/workout-runner/exercise-description/exercise-description.component.ts b/trainer/src/app/workout-runner/exercise-description/exercise-description.component.ts new file mode 100644 index 0000000..29c53fa --- /dev/null +++ b/trainer/src/app/workout-runner/exercise-description/exercise-description.component.ts @@ -0,0 +1,16 @@ +import { Component, OnInit, Input, ViewEncapsulation } from '@angular/core'; + +@Component({ + selector: 'abe-exercise-description', + templateUrl: './exercise-description.component.html', + styles: [] +}) +export class ExerciseDescriptionComponent implements OnInit { + @Input() description: string; + @Input() steps: string; + + constructor() { } + + ngOnInit() { + } +} diff --git a/trainer/src/app/workout-runner/model.ts b/trainer/src/app/workout-runner/model.ts new file mode 100644 index 0000000..868adf7 --- /dev/null +++ b/trainer/src/app/workout-runner/model.ts @@ -0,0 +1,35 @@ +export class WorkoutPlan { + constructor( + public name: string, + public title: string, + public restBetweenExercise: number, + public exercises: ExercisePlan[], + public description?: string) { + } + + totalWorkoutDuration(): number { + if (!this.exercises) { + return 0; + } + + const total = this.exercises.map((e) => e.duration).reduce((previous, current) => previous + current); + + return (this.restBetweenExercise ? this.restBetweenExercise : 0) * (this.exercises.length - 1) + total; + } +} + +export class ExercisePlan { + constructor(public exercise: Exercise, public duration: number) { + } +} + +export class Exercise { + constructor( + public name: string, + public title: string, + public description: string, + public image: string, + public nameSound?: string, + public procedure?: string, + public videos?: Array) { } +} \ No newline at end of file diff --git a/trainer/src/app/workout-runner/shared/seconds-to-time.pipe.spec.ts b/trainer/src/app/workout-runner/shared/seconds-to-time.pipe.spec.ts new file mode 100644 index 0000000..a435f77 --- /dev/null +++ b/trainer/src/app/workout-runner/shared/seconds-to-time.pipe.spec.ts @@ -0,0 +1,8 @@ +import { SecondsToTimePipe } from './seconds-to-time.pipe'; + +describe('SecondsToTimePipe', () => { + it('create an instance', () => { + const pipe = new SecondsToTimePipe(); + expect(pipe).toBeTruthy(); + }); +}); diff --git a/trainer/src/app/workout-runner/shared/seconds-to-time.pipe.ts b/trainer/src/app/workout-runner/shared/seconds-to-time.pipe.ts new file mode 100644 index 0000000..21c2a25 --- /dev/null +++ b/trainer/src/app/workout-runner/shared/seconds-to-time.pipe.ts @@ -0,0 +1,20 @@ +import { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({ + name: 'secondsToTime' +}) +export class SecondsToTimePipe implements PipeTransform { + + transform(value: number): any { + if (!isNaN(value)) { + const hours = Math.floor(value / 3600); + const minutes = Math.floor((value - (hours * 3600)) / 60); + const seconds = value - (hours * 3600) - (minutes * 60); + + return ('0' + hours).substr(-2) + ':' + + ('0' + minutes).substr(-2) + ':' + + ('0' + seconds).substr(-2); + } + return; + } +} diff --git a/trainer/src/app/workout-runner/video-player/video-player.component.html b/trainer/src/app/workout-runner/video-player/video-player.component.html new file mode 100644 index 0000000..944e312 --- /dev/null +++ b/trainer/src/app/workout-runner/video-player/video-player.component.html @@ -0,0 +1,8 @@ +
+

Videos

+
+
+ +
+
+
\ No newline at end of file diff --git a/trainer/src/app/workout-runner/video-player/video-player.component.spec.ts b/trainer/src/app/workout-runner/video-player/video-player.component.spec.ts new file mode 100644 index 0000000..e4f72b3 --- /dev/null +++ b/trainer/src/app/workout-runner/video-player/video-player.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { VideoPlayerComponent } from './video-player.component'; + +describe('VideoPlayerComponent', () => { + let component: VideoPlayerComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ VideoPlayerComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(VideoPlayerComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/trainer/src/app/workout-runner/video-player/video-player.component.ts b/trainer/src/app/workout-runner/video-player/video-player.component.ts new file mode 100644 index 0000000..621a20a --- /dev/null +++ b/trainer/src/app/workout-runner/video-player/video-player.component.ts @@ -0,0 +1,27 @@ +import { Component, OnInit, OnChanges, Input, ViewEncapsulation } from '@angular/core'; +import { SafeResourceUrl, DomSanitizer } from '@angular/platform-browser'; + +@Component({ + selector: 'abe-video-player', + templateUrl: './video-player.component.html', + styles:[] +}) +export class VideoPlayerComponent implements OnInit, OnChanges { + + private youtubeUrlPrefix = '//www.youtube.com/embed/'; + + @Input() videos: Array; + safeVideoUrls: Array; + + constructor(private sanitizer: DomSanitizer) { } + + ngOnChanges() { + this.safeVideoUrls = this.videos ? + this.videos.map(v => this.sanitizer.bypassSecurityTrustResourceUrl(this.youtubeUrlPrefix + v)) + : this.videos; + } + + ngOnInit() { + } + +} diff --git a/trainer/src/app/workout-runner/workout-runner.component.html b/trainer/src/app/workout-runner/workout-runner.component.html new file mode 100644 index 0000000..92c81d3 --- /dev/null +++ b/trainer/src/app/workout-runner/workout-runner.component.html @@ -0,0 +1,31 @@ +
+
+ +
+
+
+ +
+

Workout Remaining - {{workoutTimeRemaining | secondsToTime}}

+

{{currentExercise.exercise.title}}

+
+ +
+
+
+
+
+
+

Time Remaining: + {{currentExercise.duration-exerciseRunningDuration}} +

+

Next up: + {{workoutPlan.exercises[currentExerciseIndex + 1].exercise.title}} +

+
+
+
+ +
+
diff --git a/trainer/src/app/workout-runner/workout-runner.component.spec.ts b/trainer/src/app/workout-runner/workout-runner.component.spec.ts new file mode 100644 index 0000000..4f3ba7e --- /dev/null +++ b/trainer/src/app/workout-runner/workout-runner.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { WorkoutRunnerComponent } from './workout-runner.component'; + +describe('WorkoutRunnerComponent', () => { + let component: WorkoutRunnerComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ WorkoutRunnerComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(WorkoutRunnerComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/trainer/src/app/workout-runner/workout-runner.component.ts b/trainer/src/app/workout-runner/workout-runner.component.ts new file mode 100644 index 0000000..c3d1b42 --- /dev/null +++ b/trainer/src/app/workout-runner/workout-runner.component.ts @@ -0,0 +1,281 @@ +import { Component, OnInit, ViewEncapsulation } from '@angular/core'; +import { WorkoutPlan, ExercisePlan, Exercise } from './model'; + +@Component({ + selector: 'abe-workout-runner', + templateUrl: './workout-runner.component.html', + styles: [] +}) +export class WorkoutRunnerComponent implements OnInit { + workoutPlan: WorkoutPlan; + workoutTimeRemaining: number; + restExercise: ExercisePlan; + currentExerciseIndex: number; + currentExercise: ExercisePlan; + exerciseRunningDuration: number; + exerciseTrackingInterval: number; + workoutPaused: boolean; + + constructor() { + } + + ngOnInit() { + this.workoutPlan = this.buildWorkout(); + this.restExercise = new ExercisePlan(new Exercise('rest', 'Relax!', 'Relax a bit', 'rest.png'), this.workoutPlan.restBetweenExercise); + this.start(); + } + + start() { + this.workoutTimeRemaining = this.workoutPlan.totalWorkoutDuration(); + this.currentExerciseIndex = 0; + this.startExercise(this.workoutPlan.exercises[this.currentExerciseIndex]); + } + + pause() { + clearInterval(this.exerciseTrackingInterval); + this.workoutPaused = true; + } + + resume() { + this.startExerciseTimeTracking(); + this.workoutPaused = false; + } + + pauseResumeToggle() { + if (this.workoutPaused) { + this.resume(); + } + else { + this.pause(); + } + } + + onKeyPressed(event: KeyboardEvent) { + if (event.which === 80 || event.which === 112) { // 'p' or 'P' key to toggle pause and resume. + this.pauseResumeToggle(); + } + } + + startExercise(exercisePlan: ExercisePlan) { + this.currentExercise = exercisePlan; + this.exerciseRunningDuration = 0; + this.startExerciseTimeTracking(); + } + + startExerciseTimeTracking() { + this.exerciseTrackingInterval = window.setInterval(() => { + if (this.exerciseRunningDuration >= this.currentExercise.duration) { + clearInterval(this.exerciseTrackingInterval); + const next: ExercisePlan = this.getNextExercise(); + if (next) { + if (next !== this.restExercise) { + this.currentExerciseIndex++; + } + this.startExercise(next); + } + else { + console.log('Workout complete!'); + } + return; + } + ++this.exerciseRunningDuration; + --this.workoutTimeRemaining; + }, 1000); + } + + getNextExercise(): ExercisePlan { + let nextExercise: ExercisePlan = null; + if (this.currentExercise === this.restExercise) { + nextExercise = this.workoutPlan.exercises[this.currentExerciseIndex + 1]; + } + else if (this.currentExerciseIndex < this.workoutPlan.exercises.length - 1) { + nextExercise = this.restExercise; + } + + return nextExercise; + } + + buildWorkout(): WorkoutPlan { + const workout = new WorkoutPlan('7MinWorkout', '7 Minute Workout', 10, []); + workout.exercises.push( + new ExercisePlan( + new Exercise( + 'jumpingJacks', + 'Jumping Jacks', + 'A jumping jack or star jump, also called side-straddle hop is a physical jumping exercise.', + 'JumpingJacks.png', + 'jumpingjacks.wav', + `Assume an erect position, with feet together and arms at your side.
+ Slightly bend your knees, and propel yourself a few inches into the air.
+ While in air, bring your legs out to the side about shoulder width or slightly wider.
+ As you are moving your legs outward, you should raise your arms up over your head; arms should be + slightly bent throughout the entire in-air movement.
+ Your feet should land shoulder width or wider as your hands meet above your head with arms slightly bent`, + ['dmYwZH_BNd0', 'BABOdJ-2Z6o', 'c4DAnQ6DtF8']), + 30)); + + workout.exercises.push( + new ExercisePlan( + new Exercise( + 'wallSit', + 'Wall Sit', + 'A wall sit, also known as a Roman Chair, is an exercise done to strengthen the quadriceps muscles.', + 'wallsit.png', + 'wallsit.wav', + `Place your back against a wall with your feet shoulder width apart and a little ways out from the wall. + Then, keeping your back against the wall, lower your hips until your knees form right angles.`, + ['y-wV4Venusw', 'MMV3v4ap4ro']), + 30)); + + workout.exercises.push( + new ExercisePlan( + new Exercise( + 'pushUp', + 'Push up', + 'A push-up is a common exercise performed in a prone position by raising and lowering the body using the arms', + 'Pushup.png', + 'pushups.wav', + `Lie prone on the ground with hands placed as wide or slightly wider than shoulder width. + Keeping the body straight, lower body to the ground by bending arms at the elbows. + Raise body up off the ground by extending the arms.`, + ['Eh00_rniF8E', 'ZWdBqFLNljc', 'UwRLWMcOdwI', 'ynPwl6qyUNM', 'OicNTT2xzMI']), + 30)); + + workout.exercises.push( + new ExercisePlan( + new Exercise( + 'crunches', + 'Abdominal Crunches', + 'The basic crunch is a abdominal exercise in a strength-training program.', + 'crunches.png', + 'crunches.wav', + `Lie on your back with your knees bent and feet flat on the floor, hip-width apart. + Place your hands behind your head so your thumbs are behind your ears. + Hold your elbows out to the sides but rounded slightly in. + Gently pull your abdominals inward. + Curl up and forward so that your head, neck, and shoulder blades lift off the floor. + Hold for a moment at the top of the movement and then lower slowly back down.`, + ['Xyd_fa5zoEU', 'MKmrqcoCZ-M']), + 30)); + + workout.exercises.push( + new ExercisePlan( + new Exercise( + 'stepUpOntoChair', + 'Step Up Onto Chair', + 'Step exercises are ideal for building muscle in your lower body.', + 'stepUpOntoChair.png', + 'stepup.wav', + `Position your chair in front of you. + Stand with your feet about hip width apart, arms at your sides. + Step up onto the seat with one foot, pressing down while bringing your other foot up next to it. + Step back with the leading foot and bring the trailing foot down to finish one step-up.`, + ['aajhW7DD1EA']), + 30)); + + workout.exercises.push( + new ExercisePlan( + new Exercise( + 'squat', + 'Squat', + 'The squat is a compound, full body exercise that trains primarily the muscles of the thighs, hips, buttocks and quads.', + 'squat.png', + 'squats.wav', + `Stand with your head facing forward and your chest held up and out. + Place your feet shoulder-width apart or little wider. Extend your hands straight out in front of you. + Sit back and down like you're sitting into a chair. Keep your head facing straight as your upper body bends forward a bit. + Rather than allowing your back to round, let your lower back arch slightly as you go down. + Lower down so your thighs are parallel to the floor, with your knees over your ankles. Press your weight back into your heels. + Keep your body tight, and push through your heels to bring yourself back to the starting position.`, + ['QKKZ9AGYTi4', 'UXJrBgI2RxA']), + 30)); + + workout.exercises.push( + new ExercisePlan( + new Exercise( + 'tricepdips', + 'Tricep Dips On Chair', + 'A body weight exercise that targets triceps.', + 'tricepdips.png', + 'tricepdips.wav', + `Sit up on a chair. Your legs should be slightly extended, with your feet flat on the floor. + Place your hands edges of the chair. Your palms should be down, fingertips pointing towards the floor. + Without moving your legs, bring your glutes forward off the chair. + Steadily lower yourself. When your elbows form 90 degrees angles, push yourself back up to starting position.`, + ['tKjcgfu44sI', 'jox1rb5krQI']), + 30)); + + workout.exercises.push( + new ExercisePlan( + new Exercise( + 'plank', + 'Plank', + `The plank (also called a front hold, hover, or abdominal bridge) is an isometric core strength exercise that + involves maintaining a difficult position for extended periods of time.`, + 'Plank.png', + 'plank.wav', + `Get into pushup position on the floor. + Bend your elbows 90 degrees and rest your weight on your forearms. + Your elbows should be directly beneath your shoulders, and your body should form a straight line from head to feet. + Hold this position.`, + ['pSHjTRCQxIw', 'TvxNkmjdhMM']), + 30)); + + workout.exercises.push( + new ExercisePlan( + new Exercise( + 'highKnees', + 'High Knees', + 'A form exercise that develops strength and endurance of the hip flexors and quads and stretches the hip extensors.', + 'highknees.png', + 'highknees.wav', + `Start standing with feet hip-width apart. + Do inplace jog with your knees lifting as much as possible towards your chest.`, + ['OAJ_J3EZkdY', '8opcQdC-V-U']), + 30)); + + workout.exercises.push( + new ExercisePlan( + new Exercise( + 'lunges', + 'Lunges', + `Lunges are a good exercise for strengthening, sculpting and building several muscles/muscle groups, + including the quadriceps (or thighs), the gluteus maximus (or buttocks) as well as the hamstrings.`, + 'lunges.png', + 'lunge.wav', + `Start standing with feet hip-width apart. + Do inplace jog with your knees lifting as much as possible towards your chest.`, + ['Z2n58m2i4jg']), + 30)); + + workout.exercises.push( + new ExercisePlan( + new Exercise( + 'pushupNRotate', + 'Pushup And Rotate', + 'A variation of pushup that requires you to rotate.', + 'pushupNRotate.png', + 'pushupandrotate.wav', + `Assume the classic pushup position, but as you come up, rotate your body so your right arm lifts up and extends overhead. + Return to the starting position, lower yourself, then push up and rotate till your left hand points toward the ceiling.`, + ['qHQ_E-f5278']), + 30)); + + workout.exercises.push( + new ExercisePlan( + new Exercise( + 'sidePlank', + 'Side Plank', + 'A variation to Plank done using one hand only.', + 'sideplank.png', + 'sideplank.wav', + `Lie on your side, in a straight line from head to feet, resting on your forearm. + Your elbow should be directly under your shoulder. + With your abdominals gently contracted, lift your hips off the floor, maintaining the line. + Keep your hips square and your neck in line with your spine. Hold the position.`, + ['wqzrb67Dwf8', '_rdfjFSFKMY']), + 30)); + + return workout; + } +} diff --git a/trainer/src/app/workout-runner/workout-runner.module.ts b/trainer/src/app/workout-runner/workout-runner.module.ts new file mode 100644 index 0000000..202e714 --- /dev/null +++ b/trainer/src/app/workout-runner/workout-runner.module.ts @@ -0,0 +1,17 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { WorkoutRunnerComponent } from './workout-runner.component'; +import { ExerciseDescriptionComponent } from './exercise-description/exercise-description.component'; +import { VideoPlayerComponent } from './video-player/video-player.component'; +import { SecondsToTimePipe } from './shared/seconds-to-time.pipe'; + +@NgModule({ + imports: [ + CommonModule + ], + exports: [ + WorkoutRunnerComponent + ], + declarations: [WorkoutRunnerComponent, ExerciseDescriptionComponent, VideoPlayerComponent, SecondsToTimePipe] +}) +export class WorkoutRunnerModule { }