├── db.json
├── .gitignore
├── .DS_Store
├── src
└── js
│ ├── constants.js
│ ├── components
│ ├── .DS_Store
│ ├── Main.js
│ ├── TableData.js
│ ├── TableExerciseName.js
│ ├── Layout.js
│ ├── videos
│ │ ├── DipsVideos.js
│ │ ├── CurlsVideos.js
│ │ ├── PullUpVideos.js
│ │ ├── PulldownVideos.js
│ │ ├── CalfRaisesVideos.js
│ │ ├── FacePullsVideos.js
│ │ ├── BentOverRowVideos.js
│ │ ├── FarmersWalkVideos.js
│ │ ├── HammerCurlsVideos.js
│ │ ├── InvertedRowVideos.js
│ │ ├── BackExtensionVideos.js
│ │ ├── HangingLegRaisesVideos.js
│ │ ├── ReverseMachineFlyVideos.js
│ │ ├── HighCableChestFlyVideos.js
│ │ ├── InclineBenchPressVideos.js
│ │ ├── InclineDumbbellPressVideos.js
│ │ ├── SquatVideos.js
│ │ ├── DeadliftVideos.js
│ │ ├── StandingPressVideos.js
│ │ └── YouTube.js
│ ├── charts
│ │ ├── DeadliftChart.js
│ │ ├── PulldownChart.js
│ │ ├── BackExtensionChart.js
│ │ ├── FarmersWalkChart.js
│ │ └── DeadliftExercisesChart.js
│ ├── Layout
│ │ └── Nav.js
│ ├── Exercises.js
│ └── Exercises
│ │ └── Stopwatch.js
│ ├── action-creators
│ └── exercises.js
│ ├── reducers
│ ├── root-reducer.js
│ └── exercises-reducer.js
│ ├── containers
│ └── ExercisesContainer.js
│ ├── store.js
│ └── client.js
├── app.js
├── db
├── database.js
└── seed.js
├── server.js
├── index.html
├── webpack.config.js
└── package.json
/db.json:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
--------------------------------------------------------------------------------
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mkosowsk/strength/HEAD/.DS_Store
--------------------------------------------------------------------------------
/src/js/constants.js:
--------------------------------------------------------------------------------
1 | // Exercises
2 |
3 | export const RECEIVE_EXERCISES = 'RECEIVE_EXERCISES';
--------------------------------------------------------------------------------
/src/js/components/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mkosowsk/strength/HEAD/src/js/components/.DS_Store
--------------------------------------------------------------------------------
/app.js:
--------------------------------------------------------------------------------
1 | const Server = require('./server.js')
2 | const port = (process.env.PORT || 1337)
3 | const app = Server.app()
4 |
5 | app.listen(port)
6 | console.log(`Listening at http://localhost:${port}`)
7 |
--------------------------------------------------------------------------------
/src/js/action-creators/exercises.js:
--------------------------------------------------------------------------------
1 | import { RECEIVE_EXERCISES } from '../constants';
2 | // import axios from 'axios';
3 |
4 | export const receiveExercises = exercises => ({
5 | type: RECEIVE_EXERCISES,
6 | exercises
7 | });
--------------------------------------------------------------------------------
/src/js/reducers/root-reducer.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 | import exercisesReducer from './exercises-reducer';
3 |
4 | const rootReducer = combineReducers({
5 | exercises: exercisesReducer
6 | })
7 |
8 | export default rootReducer
9 |
--------------------------------------------------------------------------------
/src/js/components/Main.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import {browserHistory} from "react-router";
3 |
4 | export default class Squat extends React.Component {
5 | render() {
6 | return (
7 |
8 |
Strength
9 |
10 | );
11 | }
12 | }
--------------------------------------------------------------------------------
/src/js/components/TableData.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | export default class TableData extends React.Component {
4 | render() {
5 | const cellStyle = {border: '1px solid black'};
6 | return (
7 | {this.props.content} |
8 | );
9 | }
10 | }
--------------------------------------------------------------------------------
/src/js/containers/ExercisesContainer.js:
--------------------------------------------------------------------------------
1 | import Exercises from '../components/Exercises';
2 | import { connect } from 'react-redux';
3 |
4 | const mapStateToProps = (state) => {
5 | return {
6 | exercises: state.exercises.list
7 | };
8 | };
9 |
10 | export default connect(
11 | mapStateToProps
12 | )(Exercises);
--------------------------------------------------------------------------------
/db/database.js:
--------------------------------------------------------------------------------
1 | var Sequelize = require('sequelize');
2 |
3 | const db = new Sequelize('postgres://localhost:5432/strength', {
4 | logging: false
5 | });
6 |
7 | const User = db.define('users', {
8 | username: {
9 | type: Sequelize.STRING,
10 | unique: true
11 | }
12 | });
13 |
14 | module.exports = {
15 | db,
16 | User
17 | };
--------------------------------------------------------------------------------
/src/js/components/TableExerciseName.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | export default class TableExerciseName extends React.Component {
4 | render() {
5 | const cellStyle = {color: 'blue', border: '1px solid black', textDecoration: 'underline'};
6 | return (
7 | {this.props.content} |
8 | );
9 | }
10 | }
--------------------------------------------------------------------------------
/src/js/store.js:
--------------------------------------------------------------------------------
1 | import { createStore, applyMiddleware } from 'redux';
2 | import rootReducer from './reducers/root-reducer';
3 | // import createLogger from 'redux-logger'
4 | // import thunkMiddleware from 'redux-thunk'
5 |
6 | // const store = createStore(rootReducer, applyMiddleware(createLogger(), thunkMiddleware))
7 |
8 | const store = createStore(rootReducer);
9 |
10 | export default store;
11 |
12 |
--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const express = require('express')
3 |
4 | module.exports = {
5 | app: function () {
6 | const app = express()
7 | const indexPath = path.join(__dirname, 'index.html')
8 | const publicPath = express.static(path.join(__dirname, 'public'))
9 |
10 | app.use('/public', publicPath)
11 | app.get('/', function (_, res) { res.sendFile(indexPath) })
12 |
13 | return app
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Strength
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/db/seed.js:
--------------------------------------------------------------------------------
1 | const { db } = require('./database.js');
2 |
3 | const seedUsers = () => db.Promise.map(
4 | [
5 | {
6 | username: 'mitch',
7 | },
8 | {
9 | username: 'russ',
10 | }
11 | ], user => db.model('users').create(user))
12 |
13 | db.sync()
14 | .then(() => db.sync({
15 | force: true
16 | }))
17 | .then(seedUsers)
18 | .then((users) => console.log(`Seeded ${users.length} users OK`))
19 | .catch(error => console.error(error))
20 | .finally(() => db.close())
--------------------------------------------------------------------------------
/src/js/reducers/exercises-reducer.js:
--------------------------------------------------------------------------------
1 | import {
2 | RECEIVE_EXERCISES
3 | } from '../constants';
4 |
5 |
6 | const initialExercisesState = {
7 | list: []
8 | };
9 |
10 | export default function (state = initialExercisesState, action) {
11 | // console.log(action);
12 | const newState = Object.assign({}, state);
13 |
14 | switch (action.type) {
15 | case RECEIVE_EXERCISES:
16 | newState.list = action.exercises;
17 | break;
18 |
19 | // case RECEIVE_PRODUCT:
20 | // newState.selected = action.product;
21 | // break;
22 |
23 | default:
24 | return state;
25 | }
26 |
27 | return newState;
28 | }
--------------------------------------------------------------------------------
/src/js/components/Layout.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import Nav from "../components/layout/Nav";
4 |
5 | export default class Layout extends React.Component {
6 | render() {
7 | const { location } = this.props;
8 | const containerStyle = {
9 | marginTop: "60px"
10 | };
11 | return (
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | {this.props.children}
20 |
21 |
22 |
23 |
24 |
25 | );
26 | }
27 | }
--------------------------------------------------------------------------------
/src/js/components/videos/DipsVideos.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import YouTube from './YouTube';
4 |
5 | const videoOneId = 'sM6XUdt1rm4';
6 |
7 | export default class DipsVideos extends React.Component {
8 | constructor(props) {
9 | super(props);
10 |
11 | this.state = {
12 | videoOneId: videoOneId,
13 | player: null,
14 | };
15 |
16 | this.onReady = this.onReady.bind(this);
17 | }
18 |
19 | onReady(event) {
20 | this.setState({
21 | player: event.target,
22 | });
23 | }
24 |
25 | render() {
26 | return (
27 |
28 |
Dips Video
29 |
30 |
31 | );
32 | }
33 | }
34 |
35 |
--------------------------------------------------------------------------------
/src/js/components/videos/CurlsVideos.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import YouTube from './YouTube';
4 |
5 | const videoOneId = 'aEscWJ3dS3w';
6 |
7 | export default class CurlsVideos extends React.Component {
8 | constructor(props) {
9 | super(props);
10 |
11 | this.state = {
12 | videoOneId: videoOneId,
13 | player: null,
14 | };
15 |
16 | this.onReady = this.onReady.bind(this);
17 | }
18 |
19 | onReady(event) {
20 | this.setState({
21 | player: event.target,
22 | });
23 | }
24 |
25 | render() {
26 | return (
27 |
28 |
Curls Video
29 |
30 |
31 | );
32 | }
33 | }
34 |
35 |
--------------------------------------------------------------------------------
/src/js/components/videos/PullUpVideos.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import YouTube from './YouTube';
4 |
5 | const videoOneId = 'VHt8ulfWtzY';
6 |
7 | export default class PullUpVideos extends React.Component {
8 | constructor(props) {
9 | super(props);
10 |
11 | this.state = {
12 | videoOneId: videoOneId,
13 | player: null,
14 | };
15 |
16 | this.onReady = this.onReady.bind(this);
17 | }
18 |
19 | onReady(event) {
20 | this.setState({
21 | player: event.target,
22 | });
23 | }
24 |
25 | render() {
26 | return (
27 |
28 |
Pull-Up Video
29 |
30 |
31 | );
32 | }
33 | }
34 |
35 |
--------------------------------------------------------------------------------
/src/js/components/videos/PulldownVideos.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import YouTube from './YouTube';
4 |
5 | const videoOneId = 'X5n55mMqSUs';
6 |
7 | export default class PulldownVideos extends React.Component {
8 | constructor(props) {
9 | super(props);
10 |
11 | this.state = {
12 | videoOneId: videoOneId,
13 | player: null,
14 | };
15 |
16 | this.onReady = this.onReady.bind(this);
17 | }
18 |
19 | onReady(event) {
20 | this.setState({
21 | player: event.target,
22 | });
23 | }
24 |
25 | render() {
26 | return (
27 |
28 |
Pulldown Video
29 |
30 |
31 | );
32 | }
33 | }
34 |
35 |
--------------------------------------------------------------------------------
/src/js/components/videos/CalfRaisesVideos.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import YouTube from './YouTube';
4 |
5 | const videoOneId = '3UWi44yN-wM';
6 |
7 | export default class CalfRaisesVideos extends React.Component {
8 | constructor(props) {
9 | super(props);
10 |
11 | this.state = {
12 | videoOneId: videoOneId,
13 | player: null,
14 | };
15 |
16 | this.onReady = this.onReady.bind(this);
17 | }
18 |
19 | onReady(event) {
20 | this.setState({
21 | player: event.target,
22 | });
23 | }
24 |
25 | render() {
26 | return (
27 |
28 |
Calf Raises Video
29 |
30 |
31 | );
32 | }
33 | }
34 |
35 |
--------------------------------------------------------------------------------
/src/js/components/videos/FacePullsVideos.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import YouTube from './YouTube';
4 |
5 | const videoOneId = 'rep-qVOkqgk';
6 |
7 | export default class FacePullsVideos extends React.Component {
8 | constructor(props) {
9 | super(props);
10 |
11 | this.state = {
12 | videoOneId: videoOneId,
13 | player: null,
14 | };
15 |
16 | this.onReady = this.onReady.bind(this);
17 | }
18 |
19 | onReady(event) {
20 | this.setState({
21 | player: event.target,
22 | });
23 | }
24 |
25 | render() {
26 | return (
27 |
28 |
Face Pulls Video
29 |
30 |
31 | );
32 | }
33 | }
34 |
35 |
--------------------------------------------------------------------------------
/src/js/components/videos/BentOverRowVideos.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import YouTube from './YouTube';
4 |
5 | const videoOneId = '9efgcAjQe7E';
6 |
7 | export default class BentOverRowVideos extends React.Component {
8 | constructor(props) {
9 | super(props);
10 |
11 | this.state = {
12 | videoOneId: videoOneId,
13 | player: null,
14 | };
15 |
16 | this.onReady = this.onReady.bind(this);
17 | }
18 |
19 | onReady(event) {
20 | this.setState({
21 | player: event.target,
22 | });
23 | }
24 |
25 | render() {
26 | return (
27 |
28 |
Bent-Over Row Video
29 |
30 |
31 | );
32 | }
33 | }
34 |
35 |
--------------------------------------------------------------------------------
/src/js/components/videos/FarmersWalkVideos.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import YouTube from './YouTube';
4 |
5 | const videoOneId = 'Fkzk_RqlYig';
6 |
7 | export default class FarmersWalkVideos extends React.Component {
8 | constructor(props) {
9 | super(props);
10 |
11 | this.state = {
12 | videoOneId: videoOneId,
13 | player: null,
14 | };
15 |
16 | this.onReady = this.onReady.bind(this);
17 | }
18 |
19 | onReady(event) {
20 | this.setState({
21 | player: event.target,
22 | });
23 | }
24 |
25 | render() {
26 | return (
27 |
28 |
Farmer's Walk Video
29 |
30 |
31 | );
32 | }
33 | }
34 |
35 |
--------------------------------------------------------------------------------
/src/js/components/videos/HammerCurlsVideos.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import YouTube from './YouTube';
4 |
5 | const videoOneId = 'zC3nLlEvin4';
6 |
7 | export default class HammerCurlsVideos extends React.Component {
8 | constructor(props) {
9 | super(props);
10 |
11 | this.state = {
12 | videoOneId: videoOneId,
13 | player: null,
14 | };
15 |
16 | this.onReady = this.onReady.bind(this);
17 | }
18 |
19 | onReady(event) {
20 | this.setState({
21 | player: event.target,
22 | });
23 | }
24 |
25 | render() {
26 | return (
27 |
28 |
Hammer Curls Video
29 |
30 |
31 | );
32 | }
33 | }
34 |
35 |
--------------------------------------------------------------------------------
/src/js/components/videos/InvertedRowVideos.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import YouTube from './YouTube';
4 |
5 | const videoOneId = 'XZV9IwluPjw';
6 |
7 | export default class InvertedRowVideos extends React.Component {
8 | constructor(props) {
9 | super(props);
10 |
11 | this.state = {
12 | videoOneId: videoOneId,
13 | player: null,
14 | };
15 |
16 | this.onReady = this.onReady.bind(this);
17 | }
18 |
19 | onReady(event) {
20 | this.setState({
21 | player: event.target,
22 | });
23 | }
24 |
25 | render() {
26 | return (
27 |
28 |
Inverted Row Video
29 |
30 |
31 | );
32 | }
33 | }
34 |
35 |
--------------------------------------------------------------------------------
/src/js/components/videos/BackExtensionVideos.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import YouTube from './YouTube';
4 |
5 | const videoOneId = 'ph3pddpKzzw';
6 |
7 | export default class BackExtensionVideos extends React.Component {
8 | constructor(props) {
9 | super(props);
10 |
11 | this.state = {
12 | videoOneId: videoOneId,
13 | player: null,
14 | };
15 |
16 | this.onReady = this.onReady.bind(this);
17 | }
18 |
19 | onReady(event) {
20 | this.setState({
21 | player: event.target,
22 | });
23 | }
24 |
25 | render() {
26 | return (
27 |
28 |
Back Extension Video
29 |
30 |
31 | );
32 | }
33 | }
34 |
35 |
--------------------------------------------------------------------------------
/src/js/components/videos/HangingLegRaisesVideos.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import YouTube from './YouTube';
4 |
5 | const videoOneId = 'hdng3Nm1x_E';
6 |
7 | export default class HangingLegRaisesVideos extends React.Component {
8 | constructor(props) {
9 | super(props);
10 |
11 | this.state = {
12 | videoOneId: videoOneId,
13 | player: null,
14 | };
15 |
16 | this.onReady = this.onReady.bind(this);
17 | }
18 |
19 | onReady(event) {
20 | this.setState({
21 | player: event.target,
22 | });
23 | }
24 |
25 | render() {
26 | return (
27 |
28 |
Hanging Leg Raises Video
29 |
30 |
31 | );
32 | }
33 | }
34 |
35 |
--------------------------------------------------------------------------------
/src/js/components/videos/ReverseMachineFlyVideos.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import YouTube from './YouTube';
4 |
5 | const videoOneId = 'o5OvdIVV61M';
6 |
7 | export default class ReverseMachineFlyVideos extends React.Component {
8 | constructor(props) {
9 | super(props);
10 |
11 | this.state = {
12 | videoOneId: videoOneId,
13 | player: null,
14 | };
15 |
16 | this.onReady = this.onReady.bind(this);
17 | }
18 |
19 | onReady(event) {
20 | this.setState({
21 | player: event.target,
22 | });
23 | }
24 |
25 | render() {
26 | return (
27 |
28 |
Reverse Machine Fly Video
29 |
30 |
31 | );
32 | }
33 | }
34 |
35 |
--------------------------------------------------------------------------------
/src/js/components/videos/HighCableChestFlyVideos.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import YouTube from './YouTube';
4 |
5 | const videoOneId = 'Iwe6AmxVf7o';
6 |
7 | export default class HighCableChestFlyVideos extends React.Component {
8 | constructor(props) {
9 | super(props);
10 |
11 | this.state = {
12 | videoOneId: videoOneId,
13 | player: null,
14 | };
15 |
16 | this.onReady = this.onReady.bind(this);
17 | }
18 |
19 | onReady(event) {
20 | this.setState({
21 | player: event.target,
22 | });
23 | }
24 |
25 | render() {
26 | return (
27 |
28 |
High Cable Chest Fly Video
29 |
30 |
31 | );
32 | }
33 | }
34 |
35 |
--------------------------------------------------------------------------------
/src/js/components/videos/InclineBenchPressVideos.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import YouTube from './YouTube';
4 |
5 | const videoOneId = 'DbFgADa2PL8';
6 |
7 | export default class InclineBenchPressVideos extends React.Component {
8 | constructor(props) {
9 | super(props);
10 |
11 | this.state = {
12 | videoOneId: videoOneId,
13 | player: null,
14 | };
15 |
16 | this.onReady = this.onReady.bind(this);
17 | }
18 |
19 | onReady(event) {
20 | this.setState({
21 | player: event.target,
22 | });
23 | }
24 |
25 | render() {
26 | return (
27 |
28 |
Incline Bench Press Video
29 |
30 |
31 | );
32 | }
33 | }
34 |
35 |
--------------------------------------------------------------------------------
/src/js/components/videos/InclineDumbbellPressVideos.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import YouTube from './YouTube';
4 |
5 | const videoOneId = '8iPEnn-ltC8';
6 |
7 | export default class InclineDumbbellPressVideos extends React.Component {
8 | constructor(props) {
9 | super(props);
10 |
11 | this.state = {
12 | videoOneId: videoOneId,
13 | player: null,
14 | };
15 |
16 | this.onReady = this.onReady.bind(this);
17 | }
18 |
19 | onReady(event) {
20 | this.setState({
21 | player: event.target,
22 | });
23 | }
24 |
25 | render() {
26 | return (
27 |
28 |
Incline Dumbbell Press Video
29 |
30 |
31 | );
32 | }
33 | }
34 |
35 |
--------------------------------------------------------------------------------
/src/js/components/charts/DeadliftChart.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const LineChart = require("react-chartjs").Line;
4 |
5 | const lineChartData = {
6 | labels: ['9/3', '9/10', '9/17', '9/24',
7 | '10/1', '10/8', '10/15', '10/22', '10/29',
8 | '11/5', '11/12', '11/19', '11/26',
9 | '12/3', '12/10', '12/17', '12/14'],
10 | datasets: [{
11 | label: "Deadlift",
12 | strokeColor: 'red',
13 | fillColor: "rgba(0,0,0,0)",
14 | data: [415, 420, 425, 430,
15 | 435, 440, 445, 450, 455,
16 | 385, 390, 395, 400,
17 | 405, 405, 405, 405]
18 | }]
19 | }
20 |
21 | const chartOptions = {
22 | bezierCurve : false
23 | };
24 |
25 | export default class DeadliftChart extends React.Component {
26 | render() {
27 | return (
28 |
29 |
Deadlift Over Time
30 |
31 |
32 | )
33 | }
34 | }
--------------------------------------------------------------------------------
/src/js/components/charts/PulldownChart.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const LineChart = require("react-chartjs").Line;
4 |
5 | const lineChartData = {
6 | labels: ['9/3', '9/10', '9/17', '9/24',
7 | '10/1', '10/8', '10/15', '10/22', '10/29',
8 | '11/5', '11/12', '11/19', '11/26',
9 | '12/3', '12/10', '12/17', '12/14'],
10 | datasets: [{
11 | label: "Pulldown",
12 | strokeColor: 'orange',
13 | fillColor: "rgba(0,0,0,0)",
14 | data: [145, 145, 145, 145,
15 | 150, 150, 150, 150, 150,
16 | 155, 155, 155, 155,
17 | 160, 160, 160, 160]
18 | }]
19 | }
20 |
21 | const chartOptions = {
22 | bezierCurve : false
23 | };
24 |
25 | export default class PulldownChart extends React.Component {
26 | render() {
27 | return (
28 |
29 |
Pulldown Over Time
30 |
31 |
32 | )
33 | }
34 | }
--------------------------------------------------------------------------------
/src/js/components/charts/BackExtensionChart.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const LineChart = require("react-chartjs").Line;
4 |
5 | const lineChartData = {
6 | labels: ['9/3', '9/10', '9/17', '9/24',
7 | '10/1', '10/8', '10/15', '10/22', '10/29',
8 | '11/5', '11/12', '11/19', '11/26',
9 | '12/3', '12/10', '12/17', '12/14'],
10 | datasets: [{
11 | label: "Back Extension",
12 | strokeColor: 'green',
13 | fillColor: "rgba(0,0,0,0)",
14 | data: [25, 25, 25, 25,
15 | 25, 25, 25, 25, 25,
16 | 25, 25, 25, 25,
17 | 25, 25, 25, 25]
18 | }]
19 | }
20 |
21 | const chartOptions = {
22 | bezierCurve : false
23 | };
24 |
25 | export default class BackExtensionChart extends React.Component {
26 | render() {
27 | return (
28 |
29 |
Back Extension Over Time
30 |
31 |
32 | )
33 | }
34 | }
--------------------------------------------------------------------------------
/src/js/components/charts/FarmersWalkChart.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const LineChart = require("react-chartjs").Line;
4 |
5 | const lineChartData = {
6 | labels: ['9/3', '9/10', '9/17', '9/24',
7 | '10/1', '10/8', '10/15', '10/22', '10/29',
8 | '11/5', '11/12', '11/19', '11/26',
9 | '12/3', '12/10', '12/17', '12/14'],
10 | datasets: [{
11 | label: "Farmer's Walk",
12 | strokeColor: 'blue',
13 | fillColor: "rgba(0,0,0,0)",
14 | data: [85, 85, 85, 85,
15 | 85, 85, 85, 85, 85,
16 | 95, 105, 110, 115,
17 | 120, 125, 125, 125]
18 | }]
19 | }
20 |
21 | const chartOptions = {
22 | bezierCurve : false
23 | };
24 |
25 | export default class FarmersWalkChart extends React.Component {
26 | render() {
27 | return (
28 |
29 |
Farmer's Walk Over Time
30 |
31 |
32 | )
33 | }
34 | }
--------------------------------------------------------------------------------
/src/js/components/videos/SquatVideos.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import YouTube from './YouTube';
4 |
5 | const videoOneId = 'nFAscG0XUNY';
6 | const videoTwoId = 'bs_Ej32IYgo';
7 |
8 | export default class SquatVideos extends React.Component {
9 | constructor(props) {
10 | super(props);
11 |
12 | this.state = {
13 | videoOneId: videoOneId,
14 | videoTwoId: videoTwoId,
15 | player: null,
16 | };
17 |
18 | this.onReady = this.onReady.bind(this);
19 | }
20 |
21 | onReady(event) {
22 | this.setState({
23 | player: event.target,
24 | });
25 | }
26 |
27 | render() {
28 | return (
29 |
30 |
Squat Videos
31 | Beginner
32 |
33 | Advanced
34 |
35 |
36 | );
37 | }
38 | }
39 |
40 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | var debug = process.env.NODE_ENV !== "production";
2 | var webpack = require('webpack');
3 | var path = require('path');
4 |
5 | module.exports = {
6 | context: path.join(__dirname, "src"),
7 | devtool: debug ? "inline-sourcemap" : null,
8 | entry: "./js/client.js",
9 | module: {
10 | loaders: [
11 | {
12 | test: /\.jsx?$/,
13 | exclude: /(node_modules|bower_components)/,
14 | loader: 'babel-loader',
15 | query: {
16 | presets: ['react', 'es2015', 'stage-0'],
17 | plugins: ['react-html-attrs', 'transform-class-properties', 'transform-decorators-legacy'],
18 | }
19 | }
20 | ]
21 | },
22 | output: {
23 | path: __dirname + "/public/",
24 | filename: "client.min.js"
25 | },
26 | plugins: debug ? [] : [
27 | new webpack.optimize.DedupePlugin(),
28 | new webpack.optimize.OccurenceOrderPlugin(),
29 | new webpack.optimize.UglifyJsPlugin({ mangle: false, sourcemap: false }),
30 | ],
31 | };
--------------------------------------------------------------------------------
/src/js/components/videos/DeadliftVideos.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import YouTube from './YouTube';
4 |
5 | const videoOneId = '7Q_GnXm7LbI';
6 | const videoTwoId = 'Y1IGeJEXpF4';
7 |
8 | export default class DeadliftVideos extends React.Component {
9 | constructor(props) {
10 | super(props);
11 |
12 | this.state = {
13 | videoOneId: videoOneId,
14 | videoTwoId: videoTwoId,
15 | player: null,
16 | };
17 |
18 | this.onReady = this.onReady.bind(this);
19 | }
20 |
21 | onReady(event) {
22 | this.setState({
23 | player: event.target,
24 | });
25 | }
26 |
27 | render() {
28 | return (
29 |
30 |
Deadlift Videos
31 | Beginner
32 |
33 | Advanced
34 |
35 |
36 | );
37 | }
38 | }
39 |
40 |
--------------------------------------------------------------------------------
/src/js/components/videos/StandingPressVideos.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import YouTube from './YouTube';
4 |
5 | const videoOneId = '2yjwXTZQDDI';
6 | const videoTwoId = 'CnBmiBqp-AI';
7 |
8 | export default class StandingPressVideos extends React.Component {
9 | constructor(props) {
10 | super(props);
11 |
12 | this.state = {
13 | videoOneId: videoOneId,
14 | videoTwoId: videoTwoId,
15 | player: null,
16 | };
17 |
18 | this.onReady = this.onReady.bind(this);
19 | }
20 |
21 | onReady(event) {
22 | this.setState({
23 | player: event.target,
24 | });
25 | }
26 |
27 | render() {
28 | return (
29 |
30 |
Standing Press Videos
31 | Beginner
32 |
33 | Advanced
34 |
35 |
36 | );
37 | }
38 | }
39 |
40 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "strength",
3 | "version": "0.0.1",
4 | "description": "An app to help weightlifters train",
5 | "engines": {
6 | "node": "5.9.1"
7 | },
8 | "main": "webpack.config.js",
9 | "scripts": {
10 | "dev": "webpack-dev-server --content-base src",
11 | "build": "webpack --config ./webpack.config.js --progress --colors",
12 | "start": "node app.js"
13 | },
14 | "dependencies": {
15 | "babel-core": "^6.17.0",
16 | "babel-loader": "^6.2.0",
17 | "babel-plugin-add-module-exports": "^0.1.2",
18 | "babel-plugin-react-html-attrs": "^2.0.0",
19 | "babel-plugin-transform-class-properties": "^6.3.13",
20 | "babel-plugin-transform-decorators-legacy": "^1.3.4",
21 | "babel-preset-es2015": "^6.3.13",
22 | "babel-preset-react": "^6.3.13",
23 | "babel-preset-stage-0": "^6.3.13",
24 | "chart.js": "^1.1.1",
25 | "history": "^1.17.0",
26 | "react": "^0.14.8",
27 | "react-chartjs": "^0.8.0",
28 | "react-dom": "^0.14.8",
29 | "react-redux": "^5.0.2",
30 | "react-router": "^3.0.0",
31 | "redux": "^3.6.0",
32 | "webpack": "^1.12.9",
33 | "webpack-dev-server": "^1.14.1"
34 | },
35 | "repository": {
36 | "type": "git",
37 | "url": "https://github.com/mkosowsk/strength"
38 | },
39 | "keywords": [
40 | "node",
41 | "heroku",
42 | "express"
43 | ],
44 | "license": "MIT"
45 | }
46 |
--------------------------------------------------------------------------------
/src/js/components/Layout/Nav.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { IndexLink, Link } from "react-router";
3 |
4 | export default class Nav extends React.Component {
5 | constructor() {
6 | super()
7 | this.state = {
8 | collapsed: true,
9 | };
10 | }
11 |
12 | toggleCollapse() {
13 | const collapsed = !this.state.collapsed;
14 | this.setState({collapsed});
15 | }
16 |
17 | render() {
18 | const { location } = this.props;
19 | const { collapsed } = this.state;
20 | const navClass = collapsed ? "collapse" : "";
21 |
22 | return (
23 |
46 | );
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/js/components/charts/DeadliftExercisesChart.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const LineChart = require("react-chartjs").Line;
4 |
5 | const lineChartData = {
6 | labels: ['9/3', '9/10', '9/17', '9/24',
7 | '10/1', '10/8', '10/15', '10/22', '10/29',
8 | '11/5', '11/12', '11/19', '11/26',
9 | '12/3', '12/10', '12/17', '12/14'],
10 | datasets: [{
11 | label: "Deadlift",
12 | strokeColor: 'red',
13 | fillColor: "rgba(0,0,0,0)",
14 | data: [415, 420, 425, 430,
15 | 435, 440, 445, 450, 455,
16 | 385, 390, 395, 400,
17 | 405, 405, 405, 405]
18 | }, {
19 | label: "Pulldown",
20 | strokeColor: 'orange',
21 | fillColor: "rgba(0,0,0,0)",
22 | data: [145, 145, 145, 145,
23 | 150, 150, 150, 150, 150,
24 | 155, 155, 155, 155,
25 | 160, 160, 160, 160]
26 | }, {
27 | label: "Back Extension",
28 | strokeColor: 'green',
29 | fillColor: "rgba(0,0,0,0)",
30 | data: [25, 25, 25, 25,
31 | 25, 25, 25, 25, 25,
32 | 25, 25, 25, 25,
33 | 25, 25, 25, 25]
34 | }, {
35 | label: "Farmer's Walk",
36 | strokeColor: 'blue',
37 | fillColor: "rgba(0,0,0,0)",
38 | data: [85, 85, 85, 85,
39 | 85, 85, 85, 85, 85,
40 | 95, 105, 110, 115,
41 | 120, 125, 125, 125]
42 | }]
43 | }
44 |
45 | const chartOptions = {
46 | bezierCurve : false
47 | };
48 |
49 | export default class DeadliftChart extends React.Component {
50 | render() {
51 | return (
52 |
53 |
Deadlift Day Exercises Over Time
54 |
55 |
56 | )
57 | }
58 | }
--------------------------------------------------------------------------------
/src/js/components/Exercises.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import {browserHistory} from 'react-router';
3 |
4 | import TableData from "./TableData"
5 | import TableExerciseName from "./TableExerciseName"
6 |
7 | import Stopwatch from "../components/Exercises/Stopwatch";
8 |
9 | export default function(props) {
10 | const titleAndExercises = props.exercises;
11 | const title = titleAndExercises[0];
12 | const exercises = titleAndExercises.slice(1);
13 |
14 | const cellStyle = {color: 'blue', border: '1px solid black', textDecoration: 'underline'};
15 |
16 | return (
17 |
18 |
{title} Day
19 |
20 |
21 |
22 | | Exercise |
23 | Sets |
24 | Reps |
25 | browserHistory.push('/deadlift_exercises_chart') }>
27 | Weight
28 | |
29 |
30 | {
31 | exercises && exercises.map(exercise => (
32 |
33 | | browserHistory.push('/' + exercise[0].split(' ').join('_').toLowerCase() + '_videos') }>
35 | {exercise[0]}
36 | |
37 |
38 |
39 | browserHistory.push('/' + exercise[0].split(' ').join('_').toLowerCase() + '_chart') }>
41 | {exercise[3]}
42 | |
43 |
44 | )
45 | )
46 | }
47 |
48 |
49 |
50 |
51 | );
52 | }
53 |
--------------------------------------------------------------------------------
/src/js/components/Exercises/Stopwatch.js:
--------------------------------------------------------------------------------
1 | // from https://jsfiddle.net/vakhtang/j276r2zh/
2 |
3 | import React from 'react';
4 |
5 | const leftPad = (width, n) => {
6 | if ((n + '').length > width) {
7 | return n;
8 | }
9 | const padding = new Array(width).join('0');
10 | return (padding + n).slice(-width);
11 | };
12 |
13 | export default class Stopwatch extends React.Component {
14 | constructor(props) {
15 | super(props);
16 |
17 | ["lap", "update", "reset", "toggle"].forEach((method) => {
18 | this[method] = this[method].bind(this);
19 | });
20 |
21 | this.state = this.initialState = {
22 | isRunning: false,
23 | lapTimes: [],
24 | timeElapsed: 0,
25 | };
26 | }
27 | toggle() {
28 | this.setState({isRunning: !this.state.isRunning}, () => {
29 | this.state.isRunning ? this.startTimer() : clearInterval(this.timer)
30 | });
31 | }
32 | lap() {
33 | const {lapTimes, timeElapsed} = this.state;
34 | this.setState({lapTimes: lapTimes.concat(timeElapsed)});
35 | }
36 | reset() {
37 | clearInterval(this.timer);
38 | this.setState(this.initialState);
39 | }
40 | startTimer() {
41 | this.startTime = Date.now();
42 | this.timer = setInterval(this.update, 10);
43 | }
44 | update() {
45 | const delta = Date.now() - this.startTime;
46 | this.setState({timeElapsed: this.state.timeElapsed + delta});
47 | this.startTime = Date.now();
48 | }
49 | render() {
50 | const {isRunning, lapTimes, timeElapsed} = this.state;
51 | return (
52 |
53 |
54 |
57 |
63 | {lapTimes.length > 0 && }
64 |
65 | );
66 | }
67 | }
68 |
69 | class TimeElapsed extends React.Component {
70 | getUnits() {
71 | const seconds = this.props.timeElapsed / 1000;
72 | return {
73 | min: Math.floor(seconds / 60).toString(),
74 | sec: Math.floor(seconds % 60).toString(),
75 | msec: (seconds % 1).toFixed(3).substring(2)
76 | };
77 | }
78 | render() {
79 | const units = this.getUnits();
80 | return (
81 |
82 | {leftPad(2, units.min)}:
83 | {leftPad(2, units.sec)}.
84 | {units.msec}
85 |
86 | );
87 | }
88 | }
89 |
90 | class LapTimes extends React.Component {
91 | render() {
92 | const rows = this.props.lapTimes.map((lapTime, index) =>
93 |
94 | | {index} |
95 | |
96 |
97 | );
98 | return (
99 |
100 |
101 | | Lap |
102 | Time |
103 |
104 | {rows}
105 |
106 | );
107 | }
108 | }
--------------------------------------------------------------------------------
/src/js/client.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import {connect, Provider} from 'react-redux'
4 | import {browserHistory, IndexRoute, Router, Route} from 'react-router';
5 |
6 | import store from "./store";
7 |
8 | import Layout from "./components/Layout";
9 | import Main from "./components/Main";
10 |
11 | import {receiveExercises} from './action-creators/exercises';
12 | import ExercisesContainer from "./containers/ExercisesContainer";
13 |
14 | import SquatVideos from "./components/videos/SquatVideos";
15 | import StandingPressVideos from "./components/videos/StandingPressVideos";
16 | import ReverseMachineFlyVideos from "./components/videos/ReverseMachineFlyVideos";
17 | import CalfRaisesVideos from "./components/videos/CalfRaisesVideos";
18 | import HangingLegRaisesVideos from "./components/videos/HangingLegRaisesVideos";
19 |
20 | import InclineBenchPressVideos from "./components/videos/InclineBenchPressVideos";
21 | import HighCableChestFlyVideos from "./components/videos/HighCableChestFlyVideos";
22 | import HammerCurlsVideos from "./components/videos/HammerCurlsVideos";
23 | import FacePullsVideos from "./components/videos/FacePullsVideos";
24 | import InclineDumbbellPressVideos from "./components/videos/InclineDumbbellPressVideos";
25 | import DipsVideos from "./components/videos/DipsVideos";
26 |
27 | import DeadliftVideos from "./components/videos/DeadliftVideos";
28 | import PulldownVideos from "./components/videos/PulldownVideos";
29 | import BackExtensionVideos from "./components/videos/BackExtensionVideos";
30 | import FarmersWalkVideos from "./components/videos/FarmersWalkVideos";
31 |
32 | import BentOverRowVideos from "./components/videos/BentOverRowVideos";
33 | import PullUpVideos from "./components/videos/PullUpVideos";
34 | import CurlsVideos from "./components/videos/CurlsVideos";
35 | import InvertedRowVideos from "./components/videos/InvertedRowVideos";
36 |
37 | import DeadliftChart from "./components/charts/DeadliftChart";
38 | import PulldownChart from "./components/charts/PulldownChart";
39 | import BackExtensionChart from "./components/charts/BackExtensionChart";
40 | import FarmersWalkChart from "./components/charts/FarmersWalkChart";
41 |
42 | import DeadliftExercisesChart from "./components/charts/DeadliftExercisesChart";
43 |
44 | const squatExercises = [
45 | ["Squat"],
46 | ["Squat", "4", "5", "315 lbs."],
47 | ["Squat", "1", "7", "315 lbs."],
48 | ["Reverse Machine Fly", "3", "12", "175 lbs."],
49 | ["Lateral/Bent Over Raises", "3", "20", "25 lbs."],
50 | ["Romanian Deadlift", "3", "10", "185 lbs."],
51 | ];
52 |
53 | const benchExercises = [
54 | ["Bench"],
55 | ["Incline Bench Press", "3", "10", "205 lbs." ],
56 | ["Decline Bench Press", "2", "8", "225 lbs." ],
57 | ["Hammer Curls", "5", "10", "40 lbs."],
58 | ["Cardio"],
59 | ["Face Pulls", "5", "12", "95 lbs."],
60 | ["Incline Dumbbell Fly", "3", "10", "55 lbs."],
61 | ["Dips", "2", "12"],
62 | ];
63 |
64 | const deadliftExercises = [
65 | ["Deadlift"],
66 | ["Deadlift", "4", "6", "405 lbs." ],
67 | ["Pulldown", "3", "12", "180 lbs."],
68 | ["Back Extension", "3", "15", "35 lbs."],
69 | ["Ab Wheel", "3", "10"],
70 | ];
71 |
72 | const rowExercises = [
73 | ["Row"],
74 | ["Bent Over Row", "5", "10", "240 lbs." ],
75 | ["Standing Press", "3", "5", "135 lbs."],
76 | ["Pull-up", "3", "10"],
77 | ["Curls", "2", "10", "85 lbs."],
78 | ["Inverted Row", "3", "12"],
79 | ];
80 |
81 | const onSquatExercisesEnter = () => {
82 | store.dispatch(receiveExercises(squatExercises));
83 | };
84 |
85 | const onBenchExercisesEnter = () => {
86 | store.dispatch(receiveExercises(benchExercises));
87 | };
88 |
89 | const onDeadliftExercisesEnter = () => {
90 | store.dispatch(receiveExercises(deadliftExercises));
91 | };
92 |
93 | const onRowExercisesEnter = () => {
94 | store.dispatch(receiveExercises(rowExercises));
95 | };
96 |
97 | const app = document.getElementById('app');
98 |
99 | ReactDOM.render(
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 | ,
141 | app);
--------------------------------------------------------------------------------
/src/js/components/videos/YouTube.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import isEqual from 'lodash.isequal';
3 | import youTubePlayer from 'youtube-player';
4 |
5 | /**
6 | * Check whether a `props` change should result in the video being updated.
7 | *
8 | * @param {Object} prevProps
9 | * @param {Object} props
10 | */
11 | function shouldUpdateVideo(prevProps, props) {
12 | // A changing video should always trigger an update
13 | if (prevProps.videoId !== props.videoId) {
14 | return true;
15 | }
16 |
17 | // Otherwise, a change in the start/end time playerVars also requires a player
18 | // update.
19 | const prevVars = prevProps.opts.playerVars || {};
20 | const vars = props.opts.playerVars || {};
21 |
22 | return prevVars.start !== vars.start || prevVars.end !== vars.end;
23 | }
24 |
25 | /**
26 | * Neutralise API options that only require a video update, leaving only options
27 | * that require a player reset. The results can then be compared to see if a
28 | * player reset is necessary.
29 | *
30 | * @param {Object} opts
31 | */
32 | function filterResetOptions(opts) {
33 | return {
34 | ...opts,
35 | playerVars: {
36 | ...opts.playerVars,
37 | autoplay: 0,
38 | start: 0,
39 | end: 0,
40 | },
41 | };
42 | }
43 |
44 | /**
45 | * Check whether a `props` change should result in the player being reset.
46 | * The player is reset when the `props.opts` change, except if the only change
47 | * is in the `start` and `end` playerVars, because a video update can deal with
48 | * those.
49 | *
50 | * @param {Object} prevProps
51 | * @param {Object} props
52 | */
53 | function shouldResetPlayer(prevProps, props) {
54 | return !isEqual(
55 | filterResetOptions(prevProps.opts),
56 | filterResetOptions(props.opts)
57 | );
58 | }
59 |
60 | /**
61 | * Check whether a props change should result in an id or className update.
62 | *
63 | * @param {Object} prevProps
64 | * @param {Object} props
65 | */
66 | function shouldUpdatePlayer(prevProps, props) {
67 | return (
68 | prevProps.id === props.id || prevProps.className === props.className
69 | );
70 | }
71 |
72 | class YouTube extends React.Component {
73 | static propTypes = {
74 | videoId: React.PropTypes.string,
75 |
76 | // custom ID for player element
77 | id: React.PropTypes.string,
78 |
79 | // custom class name for player element
80 | className: React.PropTypes.string,
81 |
82 | // https://developers.google.com/youtube/iframe_api_reference#Loading_a_Video_Player
83 | opts: React.PropTypes.object,
84 |
85 | // event subscriptions
86 | onReady: React.PropTypes.func,
87 | onError: React.PropTypes.func,
88 | onPlay: React.PropTypes.func,
89 | onPause: React.PropTypes.func,
90 | onEnd: React.PropTypes.func,
91 | onStateChange: React.PropTypes.func,
92 | onPlaybackRateChange: React.PropTypes.func,
93 | onPlaybackQualityChange: React.PropTypes.func,
94 | };
95 |
96 | static defaultProps = {
97 | opts: {},
98 | onReady: () => {},
99 | onError: () => {},
100 | onPlay: () => {},
101 | onPause: () => {},
102 | onEnd: () => {},
103 | onStateChange: () => {},
104 | onPlaybackRateChange: () => {},
105 | onPlaybackQualityChange: () => {},
106 | };
107 |
108 | /**
109 | * Expose PlayerState constants for convenience. These constants can also be
110 | * accessed through the global YT object after the YouTube IFrame API is instantiated.
111 | * https://developers.google.com/youtube/iframe_api_reference#onStateChange
112 | */
113 | static PlayerState = {
114 | UNSTARTED: -1,
115 | ENDED: 0,
116 | PLAYING: 1,
117 | PAUSED: 2,
118 | BUFFERING: 3,
119 | CUED: 5,
120 | };
121 |
122 | constructor(props) {
123 | super(props);
124 |
125 | this.container = null;
126 | this.internalPlayer = null;
127 | }
128 |
129 | componentDidMount() {
130 | this.createPlayer();
131 | }
132 |
133 | componentDidUpdate(prevProps) {
134 | if (shouldUpdatePlayer(prevProps, this.props)) {
135 | this.updatePlayer();
136 | }
137 |
138 | if (shouldResetPlayer(prevProps, this.props)) {
139 | this.resetPlayer();
140 | }
141 |
142 | if (shouldUpdateVideo(prevProps, this.props)) {
143 | this.updateVideo();
144 | }
145 | }
146 |
147 | componentWillUnmount() {
148 | /**
149 | * Note: The `youtube-player` package that is used promisifies all Youtube
150 | * Player API calls, which introduces a delay of a tick before it actually
151 | * gets destroyed. Since React attempts to remove the element instantly
152 | * this method isn't quick enough to reset the container element.
153 | */
154 | this.internalPlayer.destroy();
155 | }
156 |
157 | /**
158 | * https://developers.google.com/youtube/iframe_api_reference#onReady
159 | *
160 | * @param {Object} event
161 | * @param {Object} target - player object
162 | */
163 | onPlayerReady = event => this.props.onReady(event);
164 |
165 | /**
166 | * https://developers.google.com/youtube/iframe_api_reference#onError
167 | *
168 | * @param {Object} event
169 | * @param {Integer} data - error type
170 | * @param {Object} target - player object
171 | */
172 | onPlayerError = event => this.props.onError(event);
173 |
174 | /**
175 | * https://developers.google.com/youtube/iframe_api_reference#onStateChange
176 | *
177 | * @param {Object} event
178 | * @param {Integer} data - status change type
179 | * @param {Object} target - actual YT player
180 | */
181 | onPlayerStateChange = (event) => {
182 | this.props.onStateChange(event);
183 | switch (event.data) {
184 |
185 | case YouTube.PlayerState.ENDED:
186 | this.props.onEnd(event);
187 | break;
188 |
189 | case YouTube.PlayerState.PLAYING:
190 | this.props.onPlay(event);
191 | break;
192 |
193 | case YouTube.PlayerState.PAUSED:
194 | this.props.onPause(event);
195 | break;
196 |
197 | default:
198 | return;
199 | }
200 | };
201 |
202 | /**
203 | * https://developers.google.com/youtube/iframe_api_reference#onPlaybackRateChange
204 | *
205 | * @param {Object} event
206 | * @param {Float} data - playback rate
207 | * @param {Object} target - actual YT player
208 | */
209 | onPlayerPlaybackRateChange = event => this.props.onPlaybackRateChange(event);
210 |
211 | /**
212 | * https://developers.google.com/youtube/iframe_api_reference#onPlaybackQualityChange
213 | *
214 | * @param {Object} event
215 | * @param {String} data - playback quality
216 | * @param {Object} target - actual YT player
217 | */
218 | onPlayerPlaybackQualityChange = event => this.props.onPlaybackQualityChange(event);
219 |
220 | /**
221 | * Initialize the Youtube Player API on the container and attach event handlers
222 | */
223 | createPlayer = () => {
224 | // do not attempt to create a player server-side, it won't work
225 | if (typeof document === 'undefined') return;
226 | // create player
227 | const playerOpts = {
228 | ...this.props.opts,
229 | // preload the `videoId` video if one is already given
230 | videoId: this.props.videoId,
231 | };
232 | this.internalPlayer = youTubePlayer(this.container, playerOpts);
233 | // attach event handlers
234 | this.internalPlayer.on('ready', this.onPlayerReady);
235 | this.internalPlayer.on('error', this.onPlayerError);
236 | this.internalPlayer.on('stateChange', this.onPlayerStateChange);
237 | this.internalPlayer.on('playbackRateChange', this.onPlayerPlaybackRateChange);
238 | this.internalPlayer.on('playbackQualityChange', this.onPlayerPlaybackQualityChange);
239 | };
240 |
241 | /**
242 | * Shorthand for destroying and then re-creating the Youtube Player
243 | */
244 | resetPlayer = () => this.internalPlayer.destroy().then(this.createPlayer);
245 |
246 | /**
247 | * Method to update the id and class of the Youtube Player iframe.
248 | * React should update this automatically but since the Youtube Player API
249 | * replaced the DIV that is mounted by React we need to do this manually.
250 | */
251 | updatePlayer = () => {
252 | this.internalPlayer.getIframe().then((iframe) => {
253 | iframe.setAttribute('id', this.props.id);
254 | iframe.setAttribute('class', this.props.className);
255 | });
256 | };
257 |
258 | /**
259 | * Call Youtube Player API methods to update the currently playing video.
260 | * Depeding on the `opts.playerVars.autoplay` this function uses one of two
261 | * Youtube Player API methods to update the video.
262 | */
263 | updateVideo = () => {
264 | if (typeof this.props.videoId === 'undefined' || this.props.videoId === null) {
265 | this.internalPlayer.stopVideo();
266 | return;
267 | }
268 |
269 | // set queueing options
270 | let autoplay = false;
271 | const opts = {
272 | videoId: this.props.videoId,
273 | };
274 | if ('playerVars' in this.props.opts) {
275 | autoplay = this.props.opts.playerVars.autoplay === 1;
276 | if ('start' in this.props.opts.playerVars) {
277 | opts.startSeconds = this.props.opts.playerVars.start;
278 | }
279 | if ('end' in this.props.opts.playerVars) {
280 | opts.endSeconds = this.props.opts.playerVars.end;
281 | }
282 | }
283 |
284 | // if autoplay is enabled loadVideoById
285 | if (autoplay) {
286 | this.internalPlayer.loadVideoById(opts);
287 | return;
288 | }
289 | // default behaviour just cues the video
290 | this.internalPlayer.cueVideoById(opts);
291 | };
292 |
293 | refContainer = (container) => {
294 | this.container = container;
295 | };
296 |
297 | render() {
298 | return (
299 |
300 |
301 |
302 | );
303 | }
304 | }
305 |
306 | export default YouTube;
307 |
--------------------------------------------------------------------------------