<commit_before>import React, { PureComponent } from 'react'
import {
  StyleSheet,
  Dimensions,
  ScrollView,
  View,
} from 'react-native'
import Mirror, { scrollviewBootstrap } from 'react-native-mirror'

const VIEWPORT_WIDTH = Dimensions.get('window').width

class ViewPager extends PureComponent {

  static defaultProps = {
    thresholdPages: 1,
    pageWidth: VIEWPORT_WIDTH,
    pagingEnabled: true,
    contentContainerStyle: {},
    renderRow: () => {},
    onPan: () => {},
    onPageChange: () => {},
    onScroll: () => {},
    disablePan: false,
    data: [],
  }

  constructor(props) {
    super(props)

    const initializedData = this._initializeData(this.props.data || [])

    this._pageWithDelta = (VIEWPORT_WIDTH - this.props.pageWidth) / 2

    this.pages = []

    this.state = {
      initializedData,
      dataSource: [...this._prepareData(initializedData)],
    }
  }

  componentDidMount() {
    setTimeout(() => {
      this.scrollView.scrollTo({
        x: (VIEWPORT_WIDTH - this._pageWithDelta) * this.props.thresholdPages,
        animated: false,
      })
    }, 0)
  }

  _initializeData = (data) => {
    return data.map((_data, index) => {
      return Object.assign({}, _data, { index })
    })
  }

  _prepareData = (data) => {

    const multiplicator = Math.ceil(this.props.thresholdPages / data.length)

    let thresholdDataFront = []
    let thresholdDataEnd = []

    for (let i = 0; i < multiplicator; i++) {
      thresholdDataFront = [...thresholdDataFront, ...[...data].reverse()]
      thresholdDataEnd = [...thresholdDataEnd, ...data]
    }

    const thresholdFront = thresholdDataFront.slice(0, this.props.thresholdPages).reverse()

    const thresholdEnd = thresholdDataEnd.slice(0, this.props.thresholdPages)

    const preparedData = [...thresholdFront, ...data, ...thresholdEnd]

    return [...preparedData]
  }

  _getPageNumberByIndex = index => {
    if (index === 0) return this.state.dataSource.length - 1
    if (index === this.state.dataSource.length - 1) return 1
    return index
  }

  _onScroll = (event) => {
    const offsetX = event.nativeEvent.contentOffset.x
    this.props.onScroll(offsetX)

    this.pageIndex = Math.ceil((offsetX / VIEWPORT_WIDTH) * 100) / 100

    if (this.pageIndex % 1 === 0) {
      if (this.pageIndex === 0) {

        this.scrollView.scrollTo({
          animated: false,
          x: VIEWPORT_WIDTH * (this.state.dataSource.length - 2),
        })

      } else if (this.pageIndex === this.state.dataSource.length - 1) {

        this.scrollView.scrollTo({
          animated: false,
          x: VIEWPORT_WIDTH,
        })

      } else {

        const pageNumber = this._getPageNumberByIndex(this.pageIndex)
        this.props.onPageChange(pageNumber)

      }
    }
  }

  scroll = dx => {
    this.scrollView.scrollTo({
      animated: false,
      x: dx / (VIEWPORT_WIDTH / (VIEWPORT_WIDTH / 2)) - this.props.pageWidth / 2 + (VIEWPORT_WIDTH / this.props.thresholdPages),
    })
  }

  scrollToPage = pageNumber => {
    this.scrollView.scrollTo({
      animated: true,
      x: (pageNumber + this.props.thresholdPages) * VIEWPORT_WIDTH,
    })
  }

  _renderRow = ({item, index}) => {

    let row = (
      <View
        key={index}
        style={[styles.rowContainer, {
          width: this.props.pageWidth,
        }]}
      >
        {this.props.renderRow({data: item})}
      </View>
    )

    if (index <= this.props.thresholdPages) {
      row = (
        <Mirror
          key={index}
          connectionId={'mirror-' + index}
          containerStyle={styles.mirror}
          mirroredProps={[
            scrollviewBootstrap,
          ]}
        >
          {row}
        </Mirror>
      )
    }

    if (index >= this.state.dataSource.length - this.props.thresholdPages - 1) {
      const idIndex = index - (this.state.dataSource.length - this.props.thresholdPages - 1)
      row = (
        <Mirror
          key={index}
          connectionId={'mirror-' + idIndex}
          containerStyle={styles.mirror}
          mirroredProps={[
            scrollviewBootstrap,
          ]}
        >
          {row}
        </Mirror>
      )
    }

    return row
  }

  render() {
    this.pages = []
    return (
      <View style={this.props.containerStyle}>
        <ScrollView
          ref={(scrollView) => {
            this.scrollView = scrollView
          }}
          horizontal={true}
          pagingEnabled={this.props.pagingEnabled}
          onScroll={this._onScroll}
          contentContainerStyle={[styles.container, this.props.contentContainerStyle, {
            width: this.props.pageWidth * this.state.dataSource.length,
          }]}>

          {this.state.dataSource.map((item, index) => {
            return this._renderRow({item, index})
          })}
        </ScrollView>
      </View>
    )
  }
}

const styles = StyleSheet.create({
  container: {
    flexDirection: 'row',
    /*
     * bug in react-native
     * overflow style has to be set
     * https://github.com/facebook/react-native/issues/12926
     */
    overflow: 'scroll',
  },
  rowContainer: {
    flexGrow: 1,
  },
  mirror: {
    flexGrow: 1,
  },
})

ViewPager.propTypes = {
  contentContainerStyle: React.PropTypes.any,
  containerStyle: React.PropTypes.any,
  data: React.PropTypes.arrayOf(
    React.PropTypes.object
  ),
  thresholdPages: React.PropTypes.number,
  pageWidth: React.PropTypes.number,
  disablePan: React.PropTypes.bool,
  pagingEnabled: React.PropTypes.bool,

  renderRow: React.PropTypes.func,
  onPageChange: React.PropTypes.func,
  onPan: React.PropTypes.func,
  onShouldSwitchToPage: React.PropTypes.func,
  onScroll: React.PropTypes.func,
}

export default ViewPager<commit_msg>experimental mirroring over prop (default = false)<commit_after>import React, { PureComponent } from 'react'
import {
  StyleSheet,
  Dimensions,
  ScrollView,
  View,
} from 'react-native'
import Mirror, { scrollviewBootstrap } from 'react-native-mirror'

const VIEWPORT_WIDTH = Dimensions.get('window').width

class ViewPager extends PureComponent {

  static defaultProps = {
    thresholdPages: 1,
    pageWidth: VIEWPORT_WIDTH,
    pagingEnabled: true,
    contentContainerStyle: {},
    renderRow: () => {},
    onPan: () => {},
    onPageChange: () => {},
    onScroll: () => {},
    disablePan: false,
    data: [],
    experimentalMirroring: false,
  }

  constructor(props) {
    super(props)

    const initializedData = this._initializeData(this.props.data || [])

    this._pageWithDelta = (VIEWPORT_WIDTH - this.props.pageWidth) / 2

    this.pages = []

    this.state = {
      initializedData,
      dataSource: [...this._prepareData(initializedData)],
    }
  }

  componentDidMount() {
    setTimeout(() => {
      this.scrollView.scrollTo({
        x: (VIEWPORT_WIDTH - this._pageWithDelta) * this.props.thresholdPages,
        animated: false,
      })
    }, 0)
  }

  _initializeData = (data) => {
    return data.map((_data, index) => {
      return Object.assign({}, _data, { index })
    })
  }

  _prepareData = (data) => {

    const multiplicator = Math.ceil(this.props.thresholdPages / data.length)

    let thresholdDataFront = []
    let thresholdDataEnd = []

    for (let i = 0; i < multiplicator; i++) {
      thresholdDataFront = [...thresholdDataFront, ...[...data].reverse()]
      thresholdDataEnd = [...thresholdDataEnd, ...data]
    }

    const thresholdFront = thresholdDataFront.slice(0, this.props.thresholdPages).reverse()

    const thresholdEnd = thresholdDataEnd.slice(0, this.props.thresholdPages)

    const preparedData = [...thresholdFront, ...data, ...thresholdEnd]

    return [...preparedData]
  }

  _getPageNumberByIndex = index => {
    if (index === 0) return this.state.dataSource.length - 1
    if (index === this.state.dataSource.length - 1) return 1
    return index
  }

  _onScroll = (event) => {
    const offsetX = event.nativeEvent.contentOffset.x
    this.props.onScroll(offsetX)

    this.pageIndex = Math.ceil((offsetX / VIEWPORT_WIDTH) * 100) / 100

    if (this.pageIndex % 1 === 0) {
      if (this.pageIndex === 0) {

        this.scrollView.scrollTo({
          animated: false,
          x: VIEWPORT_WIDTH * (this.state.dataSource.length - 2),
        })

      } else if (this.pageIndex === this.state.dataSource.length - 1) {

        this.scrollView.scrollTo({
          animated: false,
          x: VIEWPORT_WIDTH,
        })

      } else {

        const pageNumber = this._getPageNumberByIndex(this.pageIndex)
        this.props.onPageChange(pageNumber)

      }
    }
  }

  scroll = dx => {
    this.scrollView.scrollTo({
      animated: false,
      x: dx / (VIEWPORT_WIDTH / (VIEWPORT_WIDTH / 2)) - this.props.pageWidth / 2 + (VIEWPORT_WIDTH / this.props.thresholdPages),
    })
  }

  scrollToPage = pageNumber => {
    this.scrollView.scrollTo({
      animated: true,
      x: (pageNumber + this.props.thresholdPages) * VIEWPORT_WIDTH,
    })
  }

  _renderRow = ({item, index}) => {

    let row = (
      <View
        key={index}
        style={[styles.rowContainer, {
          width: this.props.pageWidth,
        }]}
      >
        {this.props.renderRow({data: item})}
      </View>
    )

    if (this.props.experimentalMirroring === true) {
      if (index <= this.props.thresholdPages) {
        row = (
          <Mirror
            key={index}
            connectionId={'mirror-' + index}
            containerStyle={styles.mirror}
            mirroredProps={[
              scrollviewBootstrap,
            ]}
          >
            {row}
          </Mirror>
        )
      }

      if (index >= this.state.dataSource.length - this.props.thresholdPages - 1) {
        const idIndex = index - (this.state.dataSource.length - this.props.thresholdPages - 1)
        row = (
          <Mirror
            key={index}
            connectionId={'mirror-' + idIndex}
            containerStyle={styles.mirror}
            mirroredProps={[
              scrollviewBootstrap,
            ]}
          >
            {row}
          </Mirror>
        )
      }
    }

    return row
  }

  render() {
    this.pages = []
    return (
      <View style={this.props.containerStyle}>
        <ScrollView
          ref={(scrollView) => {
            this.scrollView = scrollView
          }}
          horizontal={true}
          pagingEnabled={this.props.pagingEnabled}
          onScroll={this._onScroll}
          contentContainerStyle={[styles.container, this.props.contentContainerStyle, {
            width: this.props.pageWidth * this.state.dataSource.length,
          }]}>

          {this.state.dataSource.map((item, index) => {
            return this._renderRow({item, index})
          })}
        </ScrollView>
      </View>
    )
  }
}

const styles = StyleSheet.create({
  container: {
    flexDirection: 'row',
    /*
     * bug in react-native
     * overflow style has to be set
     * https://github.com/facebook/react-native/issues/12926
     */
    overflow: 'scroll',
  },
  rowContainer: {
    flexGrow: 1,
  },
  mirror: {
    flexGrow: 1,
  },
})

ViewPager.propTypes = {
  contentContainerStyle: React.PropTypes.any,
  containerStyle: React.PropTypes.any,
  data: React.PropTypes.arrayOf(
    React.PropTypes.object
  ),
  thresholdPages: React.PropTypes.number,
  pageWidth: React.PropTypes.number,
  disablePan: React.PropTypes.bool,
  pagingEnabled: React.PropTypes.bool,
  experimentalMirroring: React.PropTypes.bool,

  renderRow: React.PropTypes.func,
  onPageChange: React.PropTypes.func,
  onPan: React.PropTypes.func,
  onShouldSwitchToPage: React.PropTypes.func,
  onScroll: React.PropTypes.func,
}

export default ViewPager<|endoftext|>


<commit_before>/*eslint-env mocha */
import React from 'react';
import Route from '../Route';

import assert from 'assert';
import expect from 'expect';
import { createLocation } from 'history';
import { createRoutes } from '../RouteUtils';
import matchRoutes from '../matchRoutes';

describe('matchRoutes', function () {
  var routes, RootRoute, UsersRoute, UsersIndexRoute, UserRoute, PostRoute, FilesRoute, AboutRoute, TeamRoute, ProfileRoute, CatchAllRoute;
  beforeEach(function () {
    /*
    <Route>
      <Route path="users">
        <Index />
        <Route path=":userID">
          <Route path="/profile" />
        </Route>
        <Route path="/team" />
      </Route>
    </Route>
    <Route path="/about" />
    <Route path="*" />
    */
    routes = [
      RootRoute = {
        childRoutes: [
          UsersRoute = {
            path: 'users',
            indexRoute: (UsersIndexRoute = {}),
            childRoutes: [
              UserRoute = {
                path: ':userID',
                childRoutes: [
                  ProfileRoute = {
                    path: '/profile'
                  },
                  PostRoute = {
                    path: ':postID'
                  }
                ]
              },
              TeamRoute = {
                path: '/team'
              }
            ]
          }
        ]
      },
      FilesRoute = {
        path: '/files/*/*.jpg'
      },
      AboutRoute = {
        path: '/about'
      },
      CatchAllRoute = {
        path: '*'
      }
    ];
  });

  function describeRoutes() {
    describe('when the location matches an index route', function () {
      it('matches the correct routes', function (done) {
        matchRoutes(routes, createLocation('/users'), function (error, match) {
          assert(match);
          expect(match.routes).toEqual([ RootRoute, UsersRoute, UsersIndexRoute ]);
          done();
        });
      });
    });

    describe('when the location matches a nested route with params', function () {
      it('matches the correct routes and params', function (done) {
        matchRoutes(routes, createLocation('/users/5'), function (error, match) {
          assert(match);
          expect(match.routes).toEqual([ RootRoute, UsersRoute, UserRoute ]);
          expect(match.params).toEqual({ userID: '5' });
          done();
        });
      });
    });

    describe('when the location matches a deeply nested route with params', function () {
      it('matches the correct routes and params', function (done) {
        matchRoutes(routes, createLocation('/users/5/abc'), function (error, match) {
          assert(match);
          expect(match.routes).toEqual([ RootRoute, UsersRoute, UserRoute, PostRoute ]);
          expect(match.params).toEqual({ userID: '5', postID: 'abc' });
          done();
        });
      });
    });

    describe('when the location matches a nested route with multiple splat params', function () {
      it('matches the correct routes and params', function (done) {
        matchRoutes(routes, createLocation('/files/a/b/c.jpg'), function (error, match) {
          assert(match);
          expect(match.routes).toEqual([ FilesRoute ]);
          expect(match.params).toEqual({ splat: [ 'a', 'b/c' ] });
          done();
        });
      });
    });

    describe('when the location matches a route with hash', function () {
      it('matches the correct routes', function (done) {
        matchRoutes(routes, createLocation('/users#about'), function (error, match) {
          assert(match);
          expect(match.routes).toEqual([ RootRoute, UsersRoute, UsersIndexRoute ]);
          done();
        });
      });
    });

    describe('when the location matches a deeply nested route with params and hash', function () {
      it('matches the correct routes and params', function (done) {
        matchRoutes(routes, createLocation('/users/5/abc#about'), function (error, match) {
          assert(match);
          expect(match.routes).toEqual([ RootRoute, UsersRoute, UserRoute, PostRoute ]);
          expect(match.params).toEqual({ userID: '5', postID: 'abc' });
          done();
        });
      });
    });

    describe('when the location matches an absolute route', function () {
      it('matches the correct routes', function (done) {
        matchRoutes(routes, createLocation('/about'), function (error, match) {
          assert(match);
          expect(match.routes).toEqual([ AboutRoute ]);
          done();
        });
      });
    });

    describe('when the location does not match any routes', function () {
      it('matches the "catch-all" route', function (done) {
        matchRoutes(routes, createLocation('/not-found'), function (error, match) {
          assert(match);
          expect(match.routes).toEqual([ CatchAllRoute ]);
          done();
        });
      });
    });
  }

  describe('a synchronous route config', function () {
    describeRoutes();

    describe('when the location matches a nested absolute route', function () {
      it('matches the correct routes', function (done) {
        matchRoutes(routes, createLocation('/team'), function (error, match) {
          assert(match);
          expect(match.routes).toEqual([ RootRoute, UsersRoute, TeamRoute ]);
          done();
        });
      });
    });

    describe('when the location matches an absolute route nested under a route with params', function () {
      it('matches the correct routes and params', function (done) {
        matchRoutes(routes, createLocation('/profile'), function (error, match) {
          assert(match);
          expect(match.routes).toEqual([ RootRoute, UsersRoute, UserRoute, ProfileRoute ]);
          expect(match.params).toEqual({}); // no userID param
          done();
        });
      });
    });
  });

  describe('an asynchronous route config', function () {
    function makeAsyncRouteConfig(routes) {
      routes.forEach(function (route) {
        var { childRoutes, indexRoute } = route;

        if (childRoutes) {
          delete route.childRoutes;

          route.getChildRoutes = function (location, callback) {
            setTimeout(function () {
              callback(null, childRoutes);
            });
          };

          makeAsyncRouteConfig(childRoutes);
        }

        if (indexRoute) {
          delete route.indexRoute;

          route.getIndexRoute = function (location, callback) {
            setTimeout(function () {
              callback(null, indexRoute);
            });
          };
        }
      });
    }

    beforeEach(function () {
      makeAsyncRouteConfig(routes);
    });

    describeRoutes();
  });

  describe('an asynchronous JSX route config', function () {
    var jsxRoutes;

    beforeEach(function () {
      var getChildRoutes = function (location, callback) {
          setTimeout(function () {
            callback(null, <Route path=":userID" />);
          });
      };
      var getIndexRoute = function (location, callback) {
        setTimeout(function () {
          callback(null, <Route name='jsx' />);
        });
      };
      jsxRoutes = createRoutes([
        <Route name="users"
               path="users"
               getChildRoutes={getChildRoutes}
               getIndexRoute={getIndexRoute} />
      ]);
    });

    it('when getChildRoutes callback returns reactElements', function(done) {
      matchRoutes(jsxRoutes, createLocation('/users/5'), function (error, match) {
         assert(match);
         expect(match.routes.map(r => r.path)).toEqual([ 'users', ':userID' ]);
         expect(match.params).toEqual({ userID: '5' });
         done();
      });
    });

    it('when getIndexRoute callback returns reactElements', function(done) {
      matchRoutes(jsxRoutes, createLocation('/users'), function (error, match) {
         assert(match);
         expect(match.routes.map(r => r.name)).toEqual([ 'users', 'jsx' ]);
         done();
      });
    });
  });
});
<commit_msg>quiet you ...<commit_after>/*eslint-env mocha */
import React from 'react';
import Route from '../Route';

import assert from 'assert';
import expect from 'expect';
import { createLocation } from 'history';
import { createRoutes } from '../RouteUtils';
import matchRoutes from '../matchRoutes';

React.shutupESLint;

describe('matchRoutes', function () {
  var routes, RootRoute, UsersRoute, UsersIndexRoute, UserRoute, PostRoute, FilesRoute, AboutRoute, TeamRoute, ProfileRoute, CatchAllRoute;
  beforeEach(function () {
    /*
    <Route>
      <Route path="users">
        <Index />
        <Route path=":userID">
          <Route path="/profile" />
        </Route>
        <Route path="/team" />
      </Route>
    </Route>
    <Route path="/about" />
    <Route path="*" />
    */
    routes = [
      RootRoute = {
        childRoutes: [
          UsersRoute = {
            path: 'users',
            indexRoute: (UsersIndexRoute = {}),
            childRoutes: [
              UserRoute = {
                path: ':userID',
                childRoutes: [
                  ProfileRoute = {
                    path: '/profile'
                  },
                  PostRoute = {
                    path: ':postID'
                  }
                ]
              },
              TeamRoute = {
                path: '/team'
              }
            ]
          }
        ]
      },
      FilesRoute = {
        path: '/files/*/*.jpg'
      },
      AboutRoute = {
        path: '/about'
      },
      CatchAllRoute = {
        path: '*'
      }
    ];
  });

  function describeRoutes() {
    describe('when the location matches an index route', function () {
      it('matches the correct routes', function (done) {
        matchRoutes(routes, createLocation('/users'), function (error, match) {
          assert(match);
          expect(match.routes).toEqual([ RootRoute, UsersRoute, UsersIndexRoute ]);
          done();
        });
      });
    });

    describe('when the location matches a nested route with params', function () {
      it('matches the correct routes and params', function (done) {
        matchRoutes(routes, createLocation('/users/5'), function (error, match) {
          assert(match);
          expect(match.routes).toEqual([ RootRoute, UsersRoute, UserRoute ]);
          expect(match.params).toEqual({ userID: '5' });
          done();
        });
      });
    });

    describe('when the location matches a deeply nested route with params', function () {
      it('matches the correct routes and params', function (done) {
        matchRoutes(routes, createLocation('/users/5/abc'), function (error, match) {
          assert(match);
          expect(match.routes).toEqual([ RootRoute, UsersRoute, UserRoute, PostRoute ]);
          expect(match.params).toEqual({ userID: '5', postID: 'abc' });
          done();
        });
      });
    });

    describe('when the location matches a nested route with multiple splat params', function () {
      it('matches the correct routes and params', function (done) {
        matchRoutes(routes, createLocation('/files/a/b/c.jpg'), function (error, match) {
          assert(match);
          expect(match.routes).toEqual([ FilesRoute ]);
          expect(match.params).toEqual({ splat: [ 'a', 'b/c' ] });
          done();
        });
      });
    });

    describe('when the location matches a route with hash', function () {
      it('matches the correct routes', function (done) {
        matchRoutes(routes, createLocation('/users#about'), function (error, match) {
          assert(match);
          expect(match.routes).toEqual([ RootRoute, UsersRoute, UsersIndexRoute ]);
          done();
        });
      });
    });

    describe('when the location matches a deeply nested route with params and hash', function () {
      it('matches the correct routes and params', function (done) {
        matchRoutes(routes, createLocation('/users/5/abc#about'), function (error, match) {
          assert(match);
          expect(match.routes).toEqual([ RootRoute, UsersRoute, UserRoute, PostRoute ]);
          expect(match.params).toEqual({ userID: '5', postID: 'abc' });
          done();
        });
      });
    });

    describe('when the location matches an absolute route', function () {
      it('matches the correct routes', function (done) {
        matchRoutes(routes, createLocation('/about'), function (error, match) {
          assert(match);
          expect(match.routes).toEqual([ AboutRoute ]);
          done();
        });
      });
    });

    describe('when the location does not match any routes', function () {
      it('matches the "catch-all" route', function (done) {
        matchRoutes(routes, createLocation('/not-found'), function (error, match) {
          assert(match);
          expect(match.routes).toEqual([ CatchAllRoute ]);
          done();
        });
      });
    });
  }

  describe('a synchronous route config', function () {
    describeRoutes();

    describe('when the location matches a nested absolute route', function () {
      it('matches the correct routes', function (done) {
        matchRoutes(routes, createLocation('/team'), function (error, match) {
          assert(match);
          expect(match.routes).toEqual([ RootRoute, UsersRoute, TeamRoute ]);
          done();
        });
      });
    });

    describe('when the location matches an absolute route nested under a route with params', function () {
      it('matches the correct routes and params', function (done) {
        matchRoutes(routes, createLocation('/profile'), function (error, match) {
          assert(match);
          expect(match.routes).toEqual([ RootRoute, UsersRoute, UserRoute, ProfileRoute ]);
          expect(match.params).toEqual({}); // no userID param
          done();
        });
      });
    });
  });

  describe('an asynchronous route config', function () {
    function makeAsyncRouteConfig(routes) {
      routes.forEach(function (route) {
        var { childRoutes, indexRoute } = route;

        if (childRoutes) {
          delete route.childRoutes;

          route.getChildRoutes = function (location, callback) {
            setTimeout(function () {
              callback(null, childRoutes);
            });
          };

          makeAsyncRouteConfig(childRoutes);
        }

        if (indexRoute) {
          delete route.indexRoute;

          route.getIndexRoute = function (location, callback) {
            setTimeout(function () {
              callback(null, indexRoute);
            });
          };
        }
      });
    }

    beforeEach(function () {
      makeAsyncRouteConfig(routes);
    });

    describeRoutes();
  });

  describe('an asynchronous JSX route config', function () {
    var jsxRoutes;

    beforeEach(function () {
      var getChildRoutes = function (location, callback) {
          setTimeout(function () {
            callback(null, <Route path=":userID" />);
          });
      };
      var getIndexRoute = function (location, callback) {
        setTimeout(function () {
          callback(null, <Route name='jsx' />);
        });
      };
      jsxRoutes = createRoutes([
        <Route name="users"
               path="users"
               getChildRoutes={getChildRoutes}
               getIndexRoute={getIndexRoute} />
      ]);
    });

    it('when getChildRoutes callback returns reactElements', function(done) {
      matchRoutes(jsxRoutes, createLocation('/users/5'), function (error, match) {
         assert(match);
         expect(match.routes.map(r => r.path)).toEqual([ 'users', ':userID' ]);
         expect(match.params).toEqual({ userID: '5' });
         done();
      });
    });

    it('when getIndexRoute callback returns reactElements', function(done) {
      matchRoutes(jsxRoutes, createLocation('/users'), function (error, match) {
         assert(match);
         expect(match.routes.map(r => r.name)).toEqual([ 'users', 'jsx' ]);
         done();
      });
    });
  });
});
<|endoftext|>


<commit_before>/**
 * @author: @AngularClass
 */

const helpers = require('./helpers');
const objectMerge = require('object-merge');
const commonConfig = require('./protractor.common'); // the settings that are common to prod and dev

/**
 * Webpack Constants
 */
const ENV = process.env.NODE_ENV = process.env.ENV = 'ci';

exports.config = objectMerge(commonConfig.config, {
    sauceUser: process.env.SAUCE_USERNAME,
    sauceKey: process.env.SAUCE_ACCESS_KEY,
    sauceSeleniumAddress: 'localhost:4445/wd/hub',

    multiCapabilities: [
        /* {
         browserName: 'chrome',
         platform: 'OS X 10.11',
         name: "chrome-osx-tests",
         shardTestFiles: true,
         build: process.env.TRAVIS_BUILD_NUMBER,
         'tunnel-identifier': process.env.TRAVIS_JOB_NUMBER
         },
         {
         browserName: 'firefox',
         platform: 'OS X 10.11',
         name: "firefox-osx-tests",
         build: process.env.TRAVIS_BUILD_NUMBER,
         'tunnel-identifier': process.env.TRAVIS_JOB_NUMBER
         }, */
        {
            browserName: 'chrome',
            platform: 'Linux',
            name: 'chrome-linux-tests',
            build: process.env.TRAVIS_BUILD_NUMBER,
            screenResolution: '1280x960',
            'tunnel-identifier': process.env.TRAVIS_JOB_NUMBER
        } /*,
         {
         browserName: 'firefox',
         platform: 'Linux',
         name: "firefox-linux-tests",
         build: process.env.TRAVIS_BUILD_NUMBER,
         'tunnel-identifier': process.env.TRAVIS_JOB_NUMBER
         },
         {
         browserName: 'chrome',
         platform: 'Windows 10',
         name: "chrome-windows-tests",
         build: process.env.TRAVIS_BUILD_NUMBER,
         'tunnel-identifier': process.env.TRAVIS_JOB_NUMBER
         },
         {
         browserName: 'firefox',
         platform: 'Windows 10',
         name: "firefox-windows-tests",
         build: process.env.TRAVIS_BUILD_NUMBER,
         'tunnel-identifier': process.env.TRAVIS_JOB_NUMBER
         },
         {
         browserName: 'safari',
         platform: 'OS X 10.11',
         name: "safari-tests",
         build: process.env.TRAVIS_BUILD_NUMBER,
         'tunnel-identifier': process.env.TRAVIS_JOB_NUMBER
         },
         {
         browserName: 'MicrosoftEdge',
         name: "edge-tests",
         build: process.env.TRAVIS_BUILD_NUMBER,
         'tunnel-identifier': process.env.TRAVIS_JOB_NUMBER
         },
         {
         browserName: 'opera',
         name: "opera-tests",
         build: process.env.TRAVIS_BUILD_NUMBER,
         'tunnel-identifier': process.env.TRAVIS_JOB_NUMBER
         },
         {
         platform: 'OS X 10.10',
         browserName: 'iphone',
         name: 'ios-tests',
         varsion: '9.3',
         app: 'safari',
         deviceName: 'iPhone 6s',
         deviceOrientation: 'portrait',
         build: process.env.TRAVIS_BUILD_NUMBER,
         'tunnel-identifier': process.env.TRAVIS_JOB_NUMBER
         },
         {
         platformName: 'Android',
         browserName: 'Browser',
         name: "android-tests",
         deviceName: 'Android Emulator',
         build: process.env.TRAVIS_BUILD_NUMBER,
         'tunnel-identifier': process.env.TRAVIS_JOB_NUMBER
         } */
    ]
});
<commit_msg>fixed error with screenResolution<commit_after>/**
 * @author: @AngularClass
 */

const helpers = require('./helpers');
const objectMerge = require('object-merge');
const commonConfig = require('./protractor.common'); // the settings that are common to prod and dev

/**
 * Webpack Constants
 */
const ENV = process.env.NODE_ENV = process.env.ENV = 'ci';

exports.config = objectMerge(commonConfig.config, {
    sauceUser: process.env.SAUCE_USERNAME,
    sauceKey: process.env.SAUCE_ACCESS_KEY,
    sauceSeleniumAddress: 'localhost:4445/wd/hub',

    multiCapabilities: [
        /* {
         browserName: 'chrome',
         platform: 'OS X 10.11',
         name: "chrome-osx-tests",
         shardTestFiles: true,
         build: process.env.TRAVIS_BUILD_NUMBER,
         'tunnel-identifier': process.env.TRAVIS_JOB_NUMBER
         },
         {
         browserName: 'firefox',
         platform: 'OS X 10.11',
         name: "firefox-osx-tests",
         build: process.env.TRAVIS_BUILD_NUMBER,
         'tunnel-identifier': process.env.TRAVIS_JOB_NUMBER
         }, */
        {
            browserName: 'chrome',
            platform: 'Linux',
            name: 'chrome-linux-tests',
            build: process.env.TRAVIS_BUILD_NUMBER,
            screenResolution: '1024x768',
            'tunnel-identifier': process.env.TRAVIS_JOB_NUMBER
        } /*,
         {
         browserName: 'firefox',
         platform: 'Linux',
         name: "firefox-linux-tests",
         build: process.env.TRAVIS_BUILD_NUMBER,
         'tunnel-identifier': process.env.TRAVIS_JOB_NUMBER
         },
         {
         browserName: 'chrome',
         platform: 'Windows 10',
         name: "chrome-windows-tests",
         build: process.env.TRAVIS_BUILD_NUMBER,
         'tunnel-identifier': process.env.TRAVIS_JOB_NUMBER
         },
         {
         browserName: 'firefox',
         platform: 'Windows 10',
         name: "firefox-windows-tests",
         build: process.env.TRAVIS_BUILD_NUMBER,
         'tunnel-identifier': process.env.TRAVIS_JOB_NUMBER
         },
         {
         browserName: 'safari',
         platform: 'OS X 10.11',
         name: "safari-tests",
         build: process.env.TRAVIS_BUILD_NUMBER,
         'tunnel-identifier': process.env.TRAVIS_JOB_NUMBER
         },
         {
         browserName: 'MicrosoftEdge',
         name: "edge-tests",
         build: process.env.TRAVIS_BUILD_NUMBER,
         'tunnel-identifier': process.env.TRAVIS_JOB_NUMBER
         },
         {
         browserName: 'opera',
         name: "opera-tests",
         build: process.env.TRAVIS_BUILD_NUMBER,
         'tunnel-identifier': process.env.TRAVIS_JOB_NUMBER
         },
         {
         platform: 'OS X 10.10',
         browserName: 'iphone',
         name: 'ios-tests',
         varsion: '9.3',
         app: 'safari',
         deviceName: 'iPhone 6s',
         deviceOrientation: 'portrait',
         build: process.env.TRAVIS_BUILD_NUMBER,
         'tunnel-identifier': process.env.TRAVIS_JOB_NUMBER
         },
         {
         platformName: 'Android',
         browserName: 'Browser',
         name: "android-tests",
         deviceName: 'Android Emulator',
         build: process.env.TRAVIS_BUILD_NUMBER,
         'tunnel-identifier': process.env.TRAVIS_JOB_NUMBER
         } */
    ]
});
<|endoftext|>


<commit_before>import { Map, List } from 'immutable';

import { getIdFromModel } from '../lib/schema';
import store from '../store';

/* basic state tree layout
* {
*   $api: {
*     [url]: {
*       [resource]: {
*         models: {
*           [id]: {
*             [key]: any,
*             _fetched?: boolean
*           }
*         },
*         requests: {
*           [url]: {
*             pending?: boolean,
*             error?: Object,
*             data?: List | Map
*           }
*         }
*       }
*     }
*   }
* }
*/

export default function initApiStore( url, schema ) {

  const resourceType = schema.title;

  const methods = {

    // core

    _getState: store.getState,

    getState: key =>
      store.getState([ '$api', url, resourceType ].concat( key || [] ).map( item => String( item ) ) ),

    setState: ( val, key ) =>
      store.setState([ '$api', url, resourceType ].concat( key || [] ).map( item => String( item ) ), val ),

    setStateQuiet: ( val, key ) =>
      store.setStateQuiet([ '$api', url, resourceType ].concat( key || [] ), val ),

    // requests

    getRequests: path =>
      methods.getState([ 'requests' ].concat( path || [] ) ),

    getRequestsData: path =>
      methods.getRequests([ path, 'data' ]),

    setRequests: ( data, path ) =>
      methods.setState( data, [ 'requests' ].concat( path || [] ) ),

    setRequestsData: ( path, data ) =>
      methods.setRequests( data, [ path, 'data' ] ),

    getPending: path =>
      methods.getRequests([ path, 'pending' ]),

    setPending: ( path, data ) =>
      methods.setStateQuiet( data, [ 'requests', path, 'pending' ]),

    getError: path =>
      methods.getRequests([ path, 'error' ]),

    setError: ( path, error, options = {} ) => {
      const method = options.quiet ? methods.setStateQuiet : methods.setState;
      method( error, [ 'requests', path, 'error' ] );
    },

    // models

    getModels: () =>
      methods.getState( 'models' ) || Map(),

    setModels: data =>
      methods.setState( data, 'models' ),

    getModel: id =>
      methods.getState([ 'models', id ]),

    setModel: ( id, data ) =>
      methods.setState( data, [ 'models', id ] ),

    deleteModel: id => {
      const undo = [];
      const state = methods.getState();
      methods.setState( state.withMutations( map => {
        const model = methods.getModel( id );
        if ( model ) {
          map.set( 'models', methods.getModels().delete( id ) );
          undo.push(() => methods.setModels( methods.getModels().set( id, model ) ));
          undo.push( ...methods.removeFromCollections( map, id ) );
        }
      }));
      return undo;
    },

    updateModel: ( id, vals ) => {
      const undo = [];
      const model = methods.getModel( id );
      if ( model ) {
        methods.setModel( id, model.mergeDeep( vals ) );
        undo.push(() => methods.setModels( methods.getModels().set( id, model ) ) );
      }
      return undo;
    },

    // collections (lists hydrated with models)

    getCollection: path => {
      const collection = methods.getRequestsData( path );
      if ( collection ) {
        const models = methods.getModels();
        return collection.reduce(( memo, id ) => {
          const model = models.get( id );
          if ( model ) return memo.push( model );
          return memo;
        }, List());
      } else {
        return null;
      }
    },

    setCollection: ( data = List(), path = '/' ) => {
      const state = methods.getState();
      const nextState = state.withMutations( map => {
        const dict = data.reduce(( memo, item ) => ({ ...memo, [getIdFromModel( item )]: item }), {});
        map.set( 'models', methods.getModels().mergeDeep( dict ) );
        map.setIn([ 'requests', path, 'data' ], List( Object.keys( dict ) ) );
      });
      methods.setState( nextState );
    },

    clearCollection: ( path = '/' ) =>
      methods.setRequestsData( path, null ),

    removeFromCollections: ( map, id ) =>
      [ ...map.get( 'requests' ).entries() ]
        .reduce(( undo, [path, request] ) => {
          const collection = request.get( 'data' );
          if ( List.isList( collection ) ) {
            const modelIdx = collection.findIndex( modelId => modelId === id );
            if ( ~modelIdx ) {
              const nextCollection = collection.delete( modelIdx );
              map.setIn([ 'requests', path, 'data' ], nextCollection );
              undo.push(() => methods.setCollection( nextCollection.insert( modelIdx, id ), path ));
            }
          }
          return undo;
        }, []),

    clearAll: () => {
      methods.setRequests( Map() );
      methods.setModels( Map() );
    }
  };

  return methods;
}
<commit_msg>stringify id for raw delete<commit_after>import { Map, List } from 'immutable';

import { getIdFromModel } from '../lib/schema';
import store from '../store';

/* basic state tree layout
* {
*   $api: {
*     [url]: {
*       [resource]: {
*         models: {
*           [id]: {
*             [key]: any,
*             _fetched?: boolean
*           }
*         },
*         requests: {
*           [url]: {
*             pending?: boolean,
*             error?: Object,
*             data?: List | Map
*           }
*         }
*       }
*     }
*   }
* }
*/

export default function initApiStore( url, schema ) {

  const resourceType = schema.title;
  const rootPath = [ '$api', url, resourceType ];

  const methods = {

    // core

    _getState: store.getState,

    getState: key =>
      store.getState( rootPath.concat( key || [] ).map( item => String( item ) ) ),

    setState: ( val, key ) =>
      store.setState( rootPath.concat( key || [] ).map( item => String( item ) ), val ),

    setStateQuiet: ( val, key ) =>
      store.setStateQuiet( rootPath.concat( key || [] ), val ),

    // requests

    requestsPath: path => [ 'requests' ].concat( path || [] ),

    getRequests: path =>
      methods.getState( methods.requestsPath( path ) ),

    getRequestsData: path =>
      methods.getRequests([ path, 'data' ]),

    setRequests: ( data, path ) =>
      methods.setState( data, methods.requestsPath( path ) ),

    setRequestsData: ( path, data ) =>
      methods.setRequests( data, [ path, 'data' ] ),

    getPending: path =>
      methods.getRequests([ path, 'pending' ]),

    setPending: ( path, data ) =>
      methods.setStateQuiet( data, methods.requestsPath([ path, 'pending' ]) ),

    getError: path =>
      methods.getRequests([ path, 'error' ]),

    setError: ( path, error, options = {} ) => {
      const method = options.quiet ? methods.setStateQuiet : methods.setState;
      method( error, methods.requestsPath([ path, 'error' ]) );
    },

    // models

    getModels: () =>
      methods.getState( 'models' ) || Map(),

    setModels: data =>
      methods.setState( data, 'models' ),

    getModel: id =>
      methods.getState([ 'models', id ]),

    setModel: ( id, data ) =>
      methods.setState( data, [ 'models', id ] ),

    deleteModel: id => {
      const undo = [];
      const state = methods.getState();
      const idStr = String( id );
      methods.setState( state.withMutations( map => {
        const model = methods.getModel( idStr );
        if ( model ) {
          map.update( 'models', ( models = Map() ) => models.delete( idStr ) );
          undo.push(() => methods.setModels( methods.getModels().set( idStr, model ) ));
          undo.push( ...methods.removeFromCollections( map, idStr ) );
        }
      }));
      return undo;
    },

    updateModel: ( id, vals ) => {
      const undo = [];
      const model = methods.getModel( id );
      if ( model ) {
        methods.setModel( id, model.mergeDeep( vals ) );
        undo.push(() => methods.setModels( methods.getModels().set( id, model ) ) );
      }
      return undo;
    },

    // collections (lists hydrated with models)

    getCollection: path => {
      const collection = methods.getRequestsData( path );
      if ( collection ) {
        const models = methods.getModels();
        return collection.reduce(( memo, id ) => {
          const model = models.get( id );
          if ( model ) return memo.push( model );
          return memo;
        }, List());
      } else {
        return null;
      }
    },

    setCollection: ( data = List(), path = '/' ) => {
      const state = methods.getState();
      const nextState = state.withMutations( map => {
        const dict = data.reduce(( memo, item ) => ({ ...memo, [getIdFromModel( item )]: item }), {});
        map.update( 'models', ( models = Map() ) => models.mergeDeep( dict ) );
        map.setIn( methods.requestsPath([ path, 'data' ]), List( Object.keys( dict ) ) );
      });
      methods.setState( nextState );
    },

    clearCollection: ( path = '/' ) =>
      methods.setRequestsData( path, null ),

    removeFromCollections: ( map, id ) =>
      [ ...map.getIn( methods.requestsPath() ).entries() ]
        .reduce(( undo, [path, request] ) => {
          const collection = request.get( 'data' );
          if ( List.isList( collection ) ) {
            const modelIdx = collection.findIndex( modelId => modelId === id );
            if ( ~modelIdx ) {
              const nextCollection = collection.delete( modelIdx );
              map.setIn( methods.requestsPath([ path, 'data' ]), nextCollection );
              undo.push(() => methods.setCollection( nextCollection.insert( modelIdx, id ), path ));
            }
          }
          return undo;
        }, []),

    clearAll: () => {
      methods.setRequests( Map() );
      methods.setModels( Map() );
    }
  };

  return methods;
}
<|endoftext|>


<commit_before>import Q from 'q'
import logger from 'winston'
import moment from 'moment'
import atna from 'atna-audit'
import os from 'os'
import { UserModelAPI } from '../model/users'
import * as authorisation from './authorisation'
import * as contact from '../contact'
import { config } from '../config'
import * as utils from '../utils'
import * as auditing from '../auditing'
import crypto from 'crypto'

config.newUserExpiry = config.get('newUserExpiry')
config.userPasswordResetExpiry = config.get('userPasswordResetExpiry')
config.alerts = config.get('alerts')

const himSourceID = config.get('auditing').auditEvents.auditSourceID

/*
 * Get authentication details
 */

export async function authenticate (ctx, email) {
  email = unescape(email)

  try {
    const user = await UserModelAPI.findOne({ email: utils.caseInsensitiveRegex(email) })

    if (!user) {
      utils.logAndSetResponse(ctx, 404, `Could not find user by email ${email}`, 'info')
      // Audit unknown user requested
      let audit = atna.construct.userLoginAudit(atna.constants.OUTCOME_SERIOUS_FAILURE, himSourceID, os.hostname(), email)
      audit = atna.construct.wrapInSyslog(audit)
      return auditing.sendAuditEvent(audit, () => logger.debug('Processed internal audit'))
    } else {
      ctx.body = {
        salt: user.passwordSalt,
        ts: new Date()
      }
    }
  } catch (e) {
    return utils.logAndSetResponse(ctx, 500, `Error during authentication ${e}`, 'error')
  }
}

/**
 * Reset password Functions
 */

const passwordResetPlainMessageTemplate = (firstname, setPasswordLink) => `\
<---------- Existing User - Reset Password ---------->
Hi ${firstname},

A request has been made to reset your password on the OpenHIM instance running on ${config.alerts.himInstance}
Follow the below link to reset your password and log into OpenHIM Console
${setPasswordLink}
<---------- Existing User - Reset Password ---------->\
`

const passwordResetHtmlMessageTemplate = (firstname, setPasswordLink) => `\

Reset OpenHIM Password

Hi ${firstname},

A request has been made to reset your password on the OpenHIM instance running on ${config.alerts.himInstance}

Follow the below link to set your password and log into OpenHIM Console

${setPasswordLink}
\
`

function generateRandomToken () {
  return crypto.randomBytes(16).toString('hex')
}

/*
 * update user token/expiry and send new password email
 */
export async function userPasswordResetRequest (ctx, email) {
  email = unescape(email)

  if (email === 'root@openhim.org') {
    ctx.body = 'Cannot request password reset for \'root@openhim.org\''
    ctx.status = 403
    return
  }

  // Generate the new user token here
  // set expiry date = true
  const token = exports.generateRandomToken()
  const { duration, durationType } = config.userPasswordResetExpiry
  const expiry = moment().add(duration, durationType).utc().format()

  const updateUserTokenExpiry = {
    token,
    tokenType: 'existingUser',
    expiry
  }

  try {
    const user = await UserModelAPI.findOneAndUpdate({ email: utils.caseInsensitiveRegex(email) }, updateUserTokenExpiry)
    if (!user) {
      ctx.body = `Tried to request password reset for invalid email address: ${email}`
      ctx.status = 404
      logger.info(`Tried to request password reset for invalid email address: ${email}`)
      return
    }

    const { consoleURL } = config.alerts
    const setPasswordLink = `${consoleURL}/#/set-password/${token}`

    // Send email to user to reset password
    const plainMessage = passwordResetPlainMessageTemplate(user.firstname, setPasswordLink)
    const htmlMessage = passwordResetHtmlMessageTemplate(user.firstname, setPasswordLink)

    const sendEmail = Q.denodeify(contact.contactUser)
    const sendEmailError = await sendEmail('email', email, 'OpenHIM Console Password Reset', plainMessage, htmlMessage)
    if (sendEmailError) {
      utils.logAndSetResponse(ctx, 500, `Could not send email to user via the API ${sendEmailError}`, 'error')
      return
    }

    logger.info('The email has been sent to the user')
    ctx.body = 'Successfully set user token/expiry for password reset.'
    ctx.status = 201
    return logger.info(`User updated token/expiry for password reset ${email}`)
  } catch (error) {
    utils.logAndSetResponse(ctx, 500, `Could not update user with email ${email} via the API ${error}`, 'error')
  }
}

/**
 *New User Set Password Functions
 */

// get the new user details
export async function getUserByToken (ctx, token) {
  token = unescape(token)

  try {
    const projectionRestriction = {
      email: 1,
      firstname: 1,
      surname: 1,
      msisdn: 1,
      token: 1,
      tokenType: 1,
      locked: 1,
      expiry: 1,
      _id: 0
    }

    const result = await UserModelAPI.findOne({ token }, projectionRestriction)
    if (!result) {
      ctx.body = `User with token ${token} could not be found.`
      ctx.status = 404
    } else if (moment(result.expiry).isBefore(moment())) {
      // user- set password - expired
      ctx.body = `Token ${token} has expired`
      ctx.status = 410
    } else {
      ctx.body = result
    }
  } catch (e) {
    utils.logAndSetResponse(ctx, 500, `Could not find user with token ${token} via the API ${e}`, 'error')
  }
}

// update the password/details for the new user
export async function updateUserByToken (ctx, token) {
  let userDataExpiry
  token = unescape(token)
  const userData = ctx.request.body

  try {
    // first try get new user details to check expiry date
    userDataExpiry = await UserModelAPI.findOne({ token })

    if (!userDataExpiry) {
      ctx.body = `User with token ${token} could not be found.`
      ctx.status = 404
      return
    } else if (moment(userDataExpiry.expiry).isBefore(moment())) {
      // new user- set password - expired
      ctx.body = `User with token ${token} has expired to set their password.`
      ctx.status = 410
      return
    }
  } catch (error) {
    utils.logAndSetResponse(ctx, 500, `Could not find user with token ${token} via the API ${error}`, 'error')
    return
  }

  // check to make sure 'msisdn' isnt 'undefined' when saving
  let msisdn = null
  if (userData.msisdn) {
    msisdn = userData.msisdn
  }

  // construct user object to prevent other properties from being updated
  const userUpdateObj = {
    token: null,
    tokenType: null,
    expiry: null,
    passwordAlgorithm: userData.passwordAlgorithm,
    passwordSalt: userData.passwordSalt,
    passwordHash: userData.passwordHash
  }

  if (userDataExpiry.tokenType === 'newUser') {
    userUpdateObj.firstname = userData.firstname
    userUpdateObj.surname = userData.surname
    userUpdateObj.locked = false
    userUpdateObj.msisdn = msisdn
  }

  try {
    await UserModelAPI.findOneAndUpdate({ token }, userUpdateObj)
    ctx.body = 'Successfully set new user password.'
    return logger.info(`User updated by token ${token}`)
  } catch (error) {
    return utils.logAndSetResponse(ctx, 500, `Could not update user with token ${token} via the API ${error}`, 'error')
  }
}

/**
 New User Set Password Functions
 */

const plainMessageTemplate = (firstname, setPasswordLink) => `\
<---------- New User - Set Password ---------->
Hi ${firstname},

A profile has been created for you on the OpenHIM instance running on ${config.alerts.himInstance}
Follow the below link to set your password and log into OpenHIM Console
${setPasswordLink}
<---------- New User - Set Password ---------->\
`

const htmlMessageTemplate = (firstname, setPasswordLink) => `\

New OpenHIM Profile

Hi ${firstname},

A profile has been created for you on the OpenHIM instance running on ${config.alerts.himInstance}

Follow the below link to set your password and log into OpenHIM Console

${setPasswordLink}
\
`

/*
 * Adds a user
 */
export async function addUser (ctx) {
  // Test if the user is authorised
  if (!authorisation.inGroup('admin', ctx.authenticated)) {
    utils.logAndSetResponse(ctx, 403, `User ${ctx.authenticated.email} is not an admin, API access to addUser denied.`, 'info')
    return
  }

  const userData = ctx.request.body
  // Generate the new user token here
  // set locked = true
  // set expiry date = true

  const token = generateRandomToken()
  userData.token = token
  userData.tokenType = 'newUser'
  userData.locked = true
  userData.email = userData.email.toLowerCase()

  const { duration, durationType } = config.newUserExpiry
  userData.expiry = moment().add(duration, durationType).utc().format()

  const consoleURL = config.alerts.consoleURL
  const setPasswordLink = `${consoleURL}/#/set-password/${token}`

  try {
    const user = new UserModelAPI(userData)
    await Q.ninvoke(user, 'save')

    // Send email to new user to set password

    const plainMessage = plainMessageTemplate(userData.firstname, setPasswordLink)
    const htmlMessage = htmlMessageTemplate(userData.firstname, setPasswordLink)

    contact.contactUser('email', userData.email, 'OpenHIM Console Profile', plainMessage, htmlMessage, (err) => {
      if (err) {
        return logger.error(`The email could not be sent to the user via the API ${err}`)
      } else {
        return logger.info('The email has been sent to the new user')
      }
    })

    ctx.body = 'User successfully created'
    ctx.status = 201
    logger.info(`User ${ctx.authenticated.email} created user ${userData.email}`)
  } catch (e) {
    utils.logAndSetResponse(ctx, 500, `Could not add user via the API ${e}`, 'error')
  }
}

/*
 * Retrieves the details of a specific user
 */
export async function getUser (ctx, email) {
  email = unescape(email)

  // Test if the user is authorised, allow a user to fetch their own details
  if (!authorisation.inGroup('admin', ctx.authenticated) && (ctx.authenticated.email !== email)) {
    utils.logAndSetResponse(ctx, 403, `User ${ctx.authenticated.email} is not an admin, API access to getUser denied.`, 'info')
    return
  }

  try {
    const result = await UserModelAPI.findOne({ email: utils.caseInsensitiveRegex(email) })
    if (!result) {
      ctx.body = `User with email ${email} could not be found.`
      ctx.status = 404
    } else {
      ctx.body = result
    }
  } catch (e) {
    utils.logAndSetResponse(ctx, 500, `Could not get user via the API ${e}`, 'error')
  }
}

export async function updateUser (ctx, email) {
  email = unescape(email)

  // Test if the user is authorised, allow a user to update their own details
  if (!authorisation.inGroup('admin', ctx.authenticated) && (ctx.authenticated.email !== email)) {
    utils.logAndSetResponse(ctx, 403, `User ${ctx.authenticated.email} is not an admin, API access to updateUser denied.`, 'info')
    return
  }

  const userData = ctx.request.body

  // reset token/locked/expiry when user is updated and password supplied
  if (userData.passwordAlgorithm && userData.passwordHash && userData.passwordSalt) {
    userData.token = null
    userData.tokenType = null
    userData.locked = false
    userData.expiry = null
  }

  // Don't allow a non-admin user to change their groups
  if ((ctx.authenticated.email === email) && !authorisation.inGroup('admin', ctx.authenticated)) { delete userData.groups }

  // Ignore _id if it exists (update is by email)
  if (userData._id) { delete userData._id }

  try {
    await UserModelAPI.findOneAndUpdate({ email: utils.caseInsensitiveRegex(email) }, userData)
    ctx.body = 'Successfully updated user.'
    logger.info(`User ${ctx.authenticated.email} updated user ${userData.email}`)
  } catch (e) {
    utils.logAndSetResponse(ctx, 500, `Could not update user ${email} via the API ${e}`, 'error')
  }
}

export async function removeUser (ctx, email) {
  // Test if the user is authorised
  if (!authorisation.inGroup('admin', ctx.authenticated)) {
    utils.logAndSetResponse(ctx, 403, `User ${ctx.authenticated.email} is not an admin, API access to removeUser denied.`, 'info')
    return
  }

  email = unescape(email)

  // Test if the user is root@openhim.org
  if (email === 'root@openhim.org') {
    utils.logAndSetResponse(ctx, 403, 'User root@openhim.org is OpenHIM root, User cannot be deleted through the API', 'info')
    return
  }

  try {
    await UserModelAPI.findOneAndRemove({ email: utils.caseInsensitiveRegex(email) })
    ctx.body = `Successfully removed user with email ${email}`
    logger.info(`User ${ctx.authenticated.email} removed user ${email}`)
  } catch (e) {
    utils.logAndSetResponse(ctx, 500, `Could not remove user ${email} via the API ${e}`, 'error')
  }
}

export async function getUsers (ctx) {
  // Test if the user is authorised
  if (!authorisation.inGroup('admin', ctx.authenticated)) {
    utils.logAndSetResponse(ctx, 403, `User ${ctx.authenticated.email} is not an admin, API access to getUsers denied.`, 'info')
    return
  }

  try {
    ctx.body = await UserModelAPI.find()
  } catch (e) {
    utils.logAndSetResponse(ctx, 500, `Could not fetch all users via the API ${e}`, 'error')
  }
}
<commit_msg>Fixed breaking build<commit_after>import Q from 'q'
import logger from 'winston'
import moment from 'moment'
import atna from 'atna-audit'
import os from 'os'
import { UserModelAPI } from '../model/users'
import * as authorisation from './authorisation'
import * as contact from '../contact'
import { config } from '../config'
import * as utils from '../utils'
import * as auditing from '../auditing'
import crypto from 'crypto'

config.newUserExpiry = config.get('newUserExpiry')
config.userPasswordResetExpiry = config.get('userPasswordResetExpiry')
config.alerts = config.get('alerts')

const himSourceID = config.get('auditing').auditEvents.auditSourceID

/*
 * Get authentication details
 */

export async function authenticate (ctx, email) {
  email = unescape(email)

  try {
    const user = await UserModelAPI.findOne({ email: utils.caseInsensitiveRegex(email) })

    if (!user) {
      utils.logAndSetResponse(ctx, 404, `Could not find user by email ${email}`, 'info')
      // Audit unknown user requested
      let audit = atna.construct.userLoginAudit(atna.constants.OUTCOME_SERIOUS_FAILURE, himSourceID, os.hostname(), email)
      audit = atna.construct.wrapInSyslog(audit)
      return auditing.sendAuditEvent(audit, () => logger.debug('Processed internal audit'))
    } else {
      ctx.body = {
        salt: user.passwordSalt,
        ts: new Date()
      }
    }
  } catch (e) {
    return utils.logAndSetResponse(ctx, 500, `Error during authentication ${e}`, 'error')
  }
}

/**
 * Reset password Functions
 */

const passwordResetPlainMessageTemplate = (firstname, setPasswordLink) => `\
<---------- Existing User - Reset Password ---------->
Hi ${firstname},

A request has been made to reset your password on the OpenHIM instance running on ${config.alerts.himInstance}
Follow the below link to reset your password and log into OpenHIM Console
${setPasswordLink}
<---------- Existing User - Reset Password ---------->\
`

const passwordResetHtmlMessageTemplate = (firstname, setPasswordLink) => `\

Reset OpenHIM Password

Hi ${firstname},

A request has been made to reset your password on the OpenHIM instance running on ${config.alerts.himInstance}

Follow the below link to set your password and log into OpenHIM Console

${setPasswordLink}
\
`

function generateRandomToken () {
  return crypto.randomBytes(16).toString('hex')
}

/*
 * update user token/expiry and send new password email
 */
export async function userPasswordResetRequest (ctx, email) {
  email = unescape(email)

  if (email === 'root@openhim.org') {
    ctx.body = 'Cannot request password reset for \'root@openhim.org\''
    ctx.status = 403
    return
  }

  // Generate the new user token here
  // set expiry date = true
  const token = generateRandomToken()
  const { duration, durationType } = config.userPasswordResetExpiry
  const expiry = moment().add(duration, durationType).utc().format()

  const updateUserTokenExpiry = {
    token,
    tokenType: 'existingUser',
    expiry
  }

  try {
    const user = await UserModelAPI.findOneAndUpdate({ email: utils.caseInsensitiveRegex(email) }, updateUserTokenExpiry)
    if (!user) {
      ctx.body = `Tried to request password reset for invalid email address: ${email}`
      ctx.status = 404
      logger.info(`Tried to request password reset for invalid email address: ${email}`)
      return
    }

    const { consoleURL } = config.alerts
    const setPasswordLink = `${consoleURL}/#/set-password/${token}`

    // Send email to user to reset password
    const plainMessage = passwordResetPlainMessageTemplate(user.firstname, setPasswordLink)
    const htmlMessage = passwordResetHtmlMessageTemplate(user.firstname, setPasswordLink)

    const sendEmail = Q.denodeify(contact.contactUser)
    const sendEmailError = await sendEmail('email', email, 'OpenHIM Console Password Reset', plainMessage, htmlMessage)
    if (sendEmailError) {
      utils.logAndSetResponse(ctx, 500, `Could not send email to user via the API ${sendEmailError}`, 'error')
      return
    }

    logger.info('The email has been sent to the user')
    ctx.body = 'Successfully set user token/expiry for password reset.'
    ctx.status = 201
    return logger.info(`User updated token/expiry for password reset ${email}`)
  } catch (error) {
    utils.logAndSetResponse(ctx, 500, `Could not update user with email ${email} via the API ${error}`, 'error')
  }
}

/**
 *New User Set Password Functions
 */

// get the new user details
export async function getUserByToken (ctx, token) {
  token = unescape(token)

  try {
    const projectionRestriction = {
      email: 1,
      firstname: 1,
      surname: 1,
      msisdn: 1,
      token: 1,
      tokenType: 1,
      locked: 1,
      expiry: 1,
      _id: 0
    }

    const result = await UserModelAPI.findOne({ token }, projectionRestriction)
    if (!result) {
      ctx.body = `User with token ${token} could not be found.`
      ctx.status = 404
    } else if (moment(result.expiry).isBefore(moment())) {
      // user- set password - expired
      ctx.body = `Token ${token} has expired`
      ctx.status = 410
    } else {
      ctx.body = result
    }
  } catch (e) {
    utils.logAndSetResponse(ctx, 500, `Could not find user with token ${token} via the API ${e}`, 'error')
  }
}

// update the password/details for the new user
export async function updateUserByToken (ctx, token) {
  let userDataExpiry
  token = unescape(token)
  const userData = ctx.request.body

  try {
    // first try get new user details to check expiry date
    userDataExpiry = await UserModelAPI.findOne({ token })

    if (!userDataExpiry) {
      ctx.body = `User with token ${token} could not be found.`
      ctx.status = 404
      return
    } else if (moment(userDataExpiry.expiry).isBefore(moment())) {
      // new user- set password - expired
      ctx.body = `User with token ${token} has expired to set their password.`
      ctx.status = 410
      return
    }
  } catch (error) {
    utils.logAndSetResponse(ctx, 500, `Could not find user with token ${token} via the API ${error}`, 'error')
    return
  }

  // check to make sure 'msisdn' isnt 'undefined' when saving
  let msisdn = null
  if (userData.msisdn) {
    msisdn = userData.msisdn
  }

  // construct user object to prevent other properties from being updated
  const userUpdateObj = {
    token: null,
    tokenType: null,
    expiry: null,
    passwordAlgorithm: userData.passwordAlgorithm,
    passwordSalt: userData.passwordSalt,
    passwordHash: userData.passwordHash
  }

  if (userDataExpiry.tokenType === 'newUser') {
    userUpdateObj.firstname = userData.firstname
    userUpdateObj.surname = userData.surname
    userUpdateObj.locked = false
    userUpdateObj.msisdn = msisdn
  }

  try {
    await UserModelAPI.findOneAndUpdate({ token }, userUpdateObj)
    ctx.body = 'Successfully set new user password.'
    return logger.info(`User updated by token ${token}`)
  } catch (error) {
    return utils.logAndSetResponse(ctx, 500, `Could not update user with token ${token} via the API ${error}`, 'error')
  }
}

/**
 New User Set Password Functions
 */

const plainMessageTemplate = (firstname, setPasswordLink) => `\
<---------- New User - Set Password ---------->
Hi ${firstname},

A profile has been created for you on the OpenHIM instance running on ${config.alerts.himInstance}
Follow the below link to set your password and log into OpenHIM Console
${setPasswordLink}
<---------- New User - Set Password ---------->\
`

const htmlMessageTemplate = (firstname, setPasswordLink) => `\

New OpenHIM Profile

Hi ${firstname},

A profile has been created for you on the OpenHIM instance running on ${config.alerts.himInstance}

Follow the below link to set your password and log into OpenHIM Console

${setPasswordLink}
\
`

/*
 * Adds a user
 */
export async function addUser (ctx) {
  // Test if the user is authorised
  if (!authorisation.inGroup('admin', ctx.authenticated)) {
    utils.logAndSetResponse(ctx, 403, `User ${ctx.authenticated.email} is not an admin, API access to addUser denied.`, 'info')
    return
  }

  const userData = ctx.request.body
  // Generate the new user token here
  // set locked = true
  // set expiry date = true

  const token = generateRandomToken()
  userData.token = token
  userData.tokenType = 'newUser'
  userData.locked = true
  userData.email = userData.email.toLowerCase()

  const { duration, durationType } = config.newUserExpiry
  userData.expiry = moment().add(duration, durationType).utc().format()

  const consoleURL = config.alerts.consoleURL
  const setPasswordLink = `${consoleURL}/#/set-password/${token}`

  try {
    const user = new UserModelAPI(userData)
    await Q.ninvoke(user, 'save')

    // Send email to new user to set password

    const plainMessage = plainMessageTemplate(userData.firstname, setPasswordLink)
    const htmlMessage = htmlMessageTemplate(userData.firstname, setPasswordLink)

    contact.contactUser('email', userData.email, 'OpenHIM Console Profile', plainMessage, htmlMessage, (err) => {
      if (err) {
        return logger.error(`The email could not be sent to the user via the API ${err}`)
      } else {
        return logger.info('The email has been sent to the new user')
      }
    })

    ctx.body = 'User successfully created'
    ctx.status = 201
    logger.info(`User ${ctx.authenticated.email} created user ${userData.email}`)
  } catch (e) {
    utils.logAndSetResponse(ctx, 500, `Could not add user via the API ${e}`, 'error')
  }
}

/*
 * Retrieves the details of a specific user
 */
export async function getUser (ctx, email) {
  email = unescape(email)

  // Test if the user is authorised, allow a user to fetch their own details
  if (!authorisation.inGroup('admin', ctx.authenticated) && (ctx.authenticated.email !== email)) {
    utils.logAndSetResponse(ctx, 403, `User ${ctx.authenticated.email} is not an admin, API access to getUser denied.`, 'info')
    return
  }

  try {
    const result = await UserModelAPI.findOne({ email: utils.caseInsensitiveRegex(email) })
    if (!result) {
      ctx.body = `User with email ${email} could not be found.`
      ctx.status = 404
    } else {
      ctx.body = result
    }
  } catch (e) {
    utils.logAndSetResponse(ctx, 500, `Could not get user via the API ${e}`, 'error')
  }
}

export async function updateUser (ctx, email) {
  email = unescape(email)

  // Test if the user is authorised, allow a user to update their own details
  if (!authorisation.inGroup('admin', ctx.authenticated) && (ctx.authenticated.email !== email)) {
    utils.logAndSetResponse(ctx, 403, `User ${ctx.authenticated.email} is not an admin, API access to updateUser denied.`, 'info')
    return
  }

  const userData = ctx.request.body

  // reset token/locked/expiry when user is updated and password supplied
  if (userData.passwordAlgorithm && userData.passwordHash && userData.passwordSalt) {
    userData.token = null
    userData.tokenType = null
    userData.locked = false
    userData.expiry = null
  }

  // Don't allow a non-admin user to change their groups
  if ((ctx.authenticated.email === email) && !authorisation.inGroup('admin', ctx.authenticated)) { delete userData.groups }

  // Ignore _id if it exists (update is by email)
  if (userData._id) { delete userData._id }

  try {
    await UserModelAPI.findOneAndUpdate({ email: utils.caseInsensitiveRegex(email) }, userData)
    ctx.body = 'Successfully updated user.'
    logger.info(`User ${ctx.authenticated.email} updated user ${userData.email}`)
  } catch (e) {
    utils.logAndSetResponse(ctx, 500, `Could not update user ${email} via the API ${e}`, 'error')
  }
}

export async function removeUser (ctx, email) {
  // Test if the user is authorised
  if (!authorisation.inGroup('admin', ctx.authenticated)) {
    utils.logAndSetResponse(ctx, 403, `User ${ctx.authenticated.email} is not an admin, API access to removeUser denied.`, 'info')
    return
  }

  email = unescape(email)

  // Test if the user is root@openhim.org
  if (email === 'root@openhim.org') {
    utils.logAndSetResponse(ctx, 403, 'User root@openhim.org is OpenHIM root, User cannot be deleted through the API', 'info')
    return
  }

  try {
    await UserModelAPI.findOneAndRemove({ email: utils.caseInsensitiveRegex(email) })
    ctx.body = `Successfully removed user with email ${email}`
    logger.info(`User ${ctx.authenticated.email} removed user ${email}`)
  } catch (e) {
    utils.logAndSetResponse(ctx, 500, `Could not remove user ${email} via the API ${e}`, 'error')
  }
}

export async function getUsers (ctx) {
  // Test if the user is authorised
  if (!authorisation.inGroup('admin', ctx.authenticated)) {
    utils.logAndSetResponse(ctx, 403, `User ${ctx.authenticated.email} is not an admin, API access to getUsers denied.`, 'info')
    return
  }

  try {
    ctx.body = await UserModelAPI.find()
  } catch (e) {
    utils.logAndSetResponse(ctx, 500, `Could not fetch all users via the API ${e}`, 'error')
  }
}
<|endoftext|>


<commit_before>import React from "react"
import Helmet from "react-helmet"
import { connect } from "react-redux"

import Styles from "./About.css"
import { setCounter, getCounter, decrementCounter, incrementCounter, loadCounter } from "./CounterModule"

class About extends React.Component {
  componentWillMount() {
    if (this.props.value == null) {
      this.props.load()
    }
  }

  render() {
    return (

        <Helmet title="About" />

          Counter: {this.props.value}

          <button className={Styles.button} onClick={this.props.handleDecrement}>Decrement</button>

          <button className={Styles.button} onClick={this.props.handleIncrement}>Increment</button>

          Produced with ❤ by Sebastian Software

    )
  }
}

About.fetchData = function(props, context)
{
  // Redux' connect() add proxies for static methods, but the top-level HOC
  // does not have our required and connected state/dispatcher props.
  if (props.load) {
    return props.load()
  }

  return Promise.resolve()
}

About.propTypes = {
  value: React.PropTypes.number,
  handleIncrement: React.PropTypes.func,
  handleDecrement: React.PropTypes.func,
  reset: React.PropTypes.func
}

const mapStateToProps = (state, ownProps) => ({
  value: getCounter(state)
})

const mapDispatchToProps = (dispatch, ownProps) => ({
  handleIncrement: () => dispatch(incrementCounter()),
  handleDecrement: () => dispatch(decrementCounter()),
  load: () => dispatch(loadCounter()),
  reset: (value) => dispatch(setCounter(value == null ? 0 : value))
})

export default connect(mapStateToProps, mapDispatchToProps)(About)
<commit_msg>Cleanup<commit_after>import React from "react"
import Helmet from "react-helmet"
import { connect } from "react-redux"

import Styles from "./About.css"
import { setCounter, getCounter, decrementCounter, incrementCounter, loadCounter } from "./CounterModule"

class About extends React.Component {
  componentWillMount() {
    if (this.props.value == null) {
      this.props.load()
    }
  }

  render() {
    return (

        <Helmet title="About" />

          Counter: {this.props.value}

          <button className={Styles.button} onClick={this.props.handleDecrement}>Decrement</button>

          <button className={Styles.button} onClick={this.props.handleIncrement}>Increment</button>

          Produced with ❤ by Sebastian Software

    )
  }
}

About.fetchData = function(props, context)
{
  // Redux' connect() add proxies for static methods, but the top-level HOC
  // does not have our required and connected state/dispatcher props.
  if (props.load) {
    return props.load()
  }

  return Promise.resolve()
}

About.propTypes = {
  value: React.PropTypes.number,
  handleIncrement: React.PropTypes.func,
  handleDecrement: React.PropTypes.func
}

const mapStateToProps = (state, ownProps) => ({
  value: getCounter(state)
})

const mapDispatchToProps = (dispatch, ownProps) => ({
  handleIncrement: () => dispatch(incrementCounter()),
  handleDecrement: () => dispatch(decrementCounter()),
  load: () => dispatch(loadCounter())
})

export default connect(mapStateToProps, mapDispatchToProps)(About)
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import {
  TouchableHighlight,
} from 'react-native';

import { colors } from '../styles/styles'

const styles = {
  button: {
    alignItems: "center",
    justifyContent: "center",
    backgroundColor: colors.CYAN_DARK.clone().clearer(0.5).rgbString(),
    paddingHorizontal: 0,
    paddingVertical: 0,
    borderColor: colors.CYAN_BRIGHT.rgbString(),
    borderStyle: "solid",
    borderWidth: 1,
    borderRadius: 0, // will be calculated during button generation
  }
};

export default class RectangleButton extends Component {
  render() {

    var height = this.props.size;
    var width = this.props.size;
    var borderRadius = this.props.size / 2;

    return (
      <TouchableHighlight onPress={this.props.onPress}
                          style={[styles.button, {height: height, width: width, borderRadius: borderRadius}, this.props.style]}
                          underlayColor={colors.CYAN_DARK.clone().clearer(0.2).rgbString()}
                          activeOpacity={1}>
        <View>
          {this.props.children}
        </View>
      </TouchableHighlight>
    )

  }
}

RectangleButton.propTypes = {
  size: React.PropTypes.number.isRequired
};
<commit_msg>fixing more view errors<commit_after>import React, { Component } from 'react';
import {
  TouchableHighlight,
  View,
} from 'react-native';

import { colors } from '../styles/styles'

const styles = {
  button: {
    alignItems: "center",
    justifyContent: "center",
    backgroundColor: colors.CYAN_DARK.clone().clearer(0.5).rgbString(),
    paddingHorizontal: 0,
    paddingVertical: 0,
    borderColor: colors.CYAN_BRIGHT.rgbString(),
    borderStyle: "solid",
    borderWidth: 1,
    borderRadius: 0, // will be calculated during button generation
  }
};

export default class CircleButton extends Component {
  render() {

    var height = this.props.size;
    var width = this.props.size;
    var borderRadius = this.props.size / 2;

    return (
      <TouchableHighlight onPress={this.props.onPress}
                          style={[styles.button, {height: height, width: width, borderRadius: borderRadius}, this.props.style]}
                          underlayColor={colors.CYAN_DARK.clone().clearer(0.2).rgbString()}
                          activeOpacity={1}>
        <View>
          {this.props.children}
        </View>
      </TouchableHighlight>
    )

  }
}

CircleButton.propTypes = {
  size: React.PropTypes.number.isRequired
};
<|endoftext|>


<commit_before>// TODO:
// make a shape superclass.

function Shape() {
  this.Orientation = 0.0; // in degrees, counter clockwise, 0 is left
  this.Origin = [10000,10000]; // Anchor in world coordinates.
  this.FixedSize = true;
  this.FixedOrientation = true;
  this.LineWidth = 0; // Line width has to be in same coordiantes as points.
  this.Visibility = true; // An easy way to turn off a shape (with removing it from the shapeList).
  this.Active = false;
  this.ActiveColor = [1.0, 1.0, 0.0];
  // Playing around with layering.  The anchor is being obscured by the text.
  this.ZOffset = 0.1;
  };

Shape.prototype.destructor=function() {
  // Get rid of the buffers?
}

Shape.prototype.Draw = function (view) {
  if ( ! this.Visibility) {
    return;
  }
  if (this.Matrix == undefined) {
    this.UpdateBuffers();
  }

  if (GL) {
    // Lets use the camera to change coordinate system to pixels.
    // TODO: Put this camera in the view or viewer to avoid creating one each render.
    var camMatrix = mat4.create();
    mat4.identity(camMatrix);
    if (this.FixedSize) {
      // This camera matric changes pixel/ screen coordinate sytem to
      // view [-1,1],[-1,1],z
      camMatrix[0] = 2.0 / view.Viewport[2];
      camMatrix[12] = -1.0;
      camMatrix[5] = 2.0 / view.Viewport[3];
      camMatrix[13] = -1.0;
      camMatrix[14] = viewFrontZ; // In front of tiles in this view
    }

    // The actor matrix that rotates to orientation and shift (0,0) to origin.
    // Rotate based on ivar orientation.
    var theta = this.Orientation * 3.1415926536 / 180.0;
    this.Matrix[0] =  Math.cos(theta);
    this.Matrix[1] =  Math.sin(theta);
    this.Matrix[4] = -Math.sin(theta);
    this.Matrix[5] =  Math.cos(theta);
    // Place the origin of the shape.
    x = this.Origin[0];
    y = this.Origin[1];
    if (this.FixedSize) {
      // For fixed size, translation must be in view/pixel coordinates.
      // First transform the world to view.
      var m = view.Camera.Matrix;
      var x = (this.Origin[0]*m[0] + this.Origin[1]*m[4] + m[12])/m[15];
      var y = (this.Origin[0]*m[1] + this.Origin[1]*m[5] + m[13])/m[15];
      // convert view to pixels (view coordinate ssytem).
      x = view.Viewport[2]*(0.5*(x+1.0));
      y = view.Viewport[3]*(0.5*(y+1.0));
    }
    // Translate to place the origin.
    this.Matrix[12] = x;
    this.Matrix[13] = y;
    this.Matrix[14] = this.ZOffset;

    var program = polyProgram;

    GL.useProgram(program);
    GL.disable(GL.BLEND);
    GL.enable(GL.DEPTH_TEST);

    // This does not work.
    // I will need to make thick lines with polygons.
    //GL.lineWidth(5);

    // These are the same for every tile.
    // Vertex points (shifted by tiles matrix)
    GL.bindBuffer(GL.ARRAY_BUFFER, this.VertexPositionBuffer);
    // Needed for outline ??? For some reason, DrawOutline did not work
    // without this call first.
    GL.vertexAttribPointer(program.vertexPositionAttribute,
                           this.VertexPositionBuffer.itemSize,
                           GL.FLOAT, false, 0, 0);     // Texture coordinates
    // Local view.
    GL.viewport(view.Viewport[0], view.Viewport[1],
                view.Viewport[2], view.Viewport[3]);

    var viewFrontZ = view.Camera.ZRange[0]+0.01;

    GL.uniformMatrix4fv(program.mvMatrixUniform, false, this.Matrix);
    if (this.FixedSize) {
      GL.uniformMatrix4fv(program.pMatrixUniform, false, camMatrix);
    } else {
      // Use main views camera to convert world to view.
      GL.uniformMatrix4fv(program.pMatrixUniform, false, view.Camera.Matrix);
    }

    // Fill color
    if (this.FillColor != undefined) {
      if (this.Active) {
        GL.uniform3f(program.colorUniform, this.ActiveColor[0],
                     this.ActiveColor[1], this.ActiveColor[2]);
      } else {
        GL.uniform3f(program.colorUniform, this.FillColor[0],
                     this.FillColor[1], this.FillColor[2]);
      }
      // Cell Connectivity
      GL.bindBuffer(GL.ELEMENT_ARRAY_BUFFER, this.CellBuffer);

      GL.drawElements(GL.TRIANGLES, this.CellBuffer.numItems,
                      GL.UNSIGNED_SHORT,0);
    }

    if (this.OutlineColor != undefined) {
      if (this.Active) {
        GL.uniform3f(program.colorUniform, this.ActiveColor[0],
                     this.ActiveColor[1], this.ActiveColor[2]);
      } else {
        GL.uniform3f(program.colorUniform, this.OutlineColor[0],
                     this.OutlineColor[1], this.OutlineColor[2]);
      }

      if (this.LineWidth == 0) {
        if (this.WireFrame) {
          GL.bindBuffer(GL.ELEMENT_ARRAY_BUFFER, this.CellBuffer);
          GL.drawElements(GL.LINE_LOOP, this.CellBuffer.numItems,
                      GL.UNSIGNED_SHORT,0);
        } else {
          // Outline. This only works for polylines
          GL.drawArrays(GL.LINE_STRIP, 0, this.VertexPositionBuffer.numItems);
        }
      } else {
        // Cell Connectivity
        GL.bindBuffer(GL.ELEMENT_ARRAY_BUFFER, this.LineCellBuffer);
        GL.drawElements(GL.TRIANGLES, this.LineCellBuffer.numItems,
                        GL.UNSIGNED_SHORT,0);
      }
    }
  } else { // 2d Canvas -----------------------------------------------
    view.Context2d.save();
    // Flip y axis
    view.Context2d.setTransform(1,0,0,-1,0,view.Viewport[3]);
    var theta = this.Orientation * 3.1415926536 / 180.0;
    this.Matrix[0] =  Math.cos(theta);
    this.Matrix[1] =  Math.sin(theta);
    this.Matrix[4] = -Math.sin(theta);
    this.Matrix[5] =  Math.cos(theta);
    // Place the origin of the shape.
    x = this.Origin[0];
    y = this.Origin[1];
    var scale = 1.0;
    if ( ! this.FixedSize) {
      // World need to be drawn in view coordinate system so the
      scale = view.Viewport[3] / view.Camera.GetHeight();
    }
    // First transform the origin-world to view.
    var m = view.Camera.Matrix;
    var x = (this.Origin[0]*m[0] + this.Origin[1]*m[4] + m[12])/m[15];
    var y = (this.Origin[0]*m[1] + this.Origin[1]*m[5] + m[13])/m[15];
    // convert origin-view to pixels (view coordinate system).
    x = view.Viewport[2]*(0.5*(x+1.0));
    y = view.Viewport[3]*(0.5*(y+1.0));
    view.Context2d.transform(this.Matrix[0],this.Matrix[1],this.Matrix[4],this.Matrix[5],x,y);
    view.Context2d.beginPath();
    view.Context2d.moveTo(this.PointBuffer[0]*scale,this.PointBuffer[1]*scale);
    var i = 3;
    while ( i < this.PointBuffer.length ) {
      view.Context2d.lineTo(this.PointBuffer[i]*scale,this.PointBuffer[i+1]*scale);
      i += 3;
    }

    if (this.OutlineColor != undefined) {
      var width = this.LineWidth * scale;
      if (width == 0) {
        width = 1;
      }
      view.Context2d.lineWidth = width;
      if (this.Active) {
        view.Context2d.strokeStyle=ConvertColorToHex(this.ActiveColor);
      } else {
        view.Context2d.strokeStyle=ConvertColorToHex(this.OutlineColor);
      }
      view.Context2d.stroke();
    }

    if (this.FillColor != undefined) {
      if (this.Active) {
        view.Context2d.fillStyle=ConvertColorToHex(this.ActiveColor);
      } else {
        view.Context2d.fillStyle=ConvertColorToHex(this.FillColor);
      }
      view.Context2d.fill();
    }

    view.Context2d.restore();
  }
}

// Invert the fill color.
Shape.prototype.ChooseOutlineColor = function () {
  if (this.FillColor) {
    this.OutlineColor = [1.0-this.FillColor[0],
                         1.0-this.FillColor[1],
                         1.0-this.FillColor[2]];

  }
}

Shape.prototype.SetOutlineColor = function (c) {
  this.OutlineColor = ConvertColor(c);
}

Shape.prototype.SetFillColor = function (c) {
  this.FillColor = ConvertColor(c);
}

Shape.prototype.HandleMouseMove = function(event, dx,dy) {
  // superclass does nothing
  return false;
}

//Shape.prototype.UpdateBuffers = function() {
    //    // The superclass does not implement this method.
//}

Shape.prototype.IntersectPointLine = function(pt, end0, end1, thickness) {
  // make end0 the origin.
  var x = pt[0] - end0[0];
  var y = pt[1] - end0[1];
  var vx = end1[0] - end0[0];
  var vy = end1[1] - end0[1];

  // Rotate so the edge lies on the x axis.
  var length = Math.sqrt(vx*vx + vy*vy); // Avoid atan2 ... with clever use of complex numbers.
  vx = vx/length;
  vy = -vy/length;
  var newX = (x*vx - y*vy);
  var newY = (x*vy + y*vx);

  if (newX >= 0.0 && newX <= length) {
    if (Math.abs(newY) < (thickness *0.5)) {
      return true;
    }
  return false;
  }
}

<commit_msg>Fixes for stack and comparison.  Also Arrow is working again in GL<commit_after>// TODO:
// make a shape superclass.

function Shape() {
  this.Orientation = 0.0; // in degrees, counter clockwise, 0 is left
  this.Origin = [10000,10000]; // Anchor in world coordinates.
  this.FixedSize = true;
  this.FixedOrientation = true;
  this.LineWidth = 0; // Line width has to be in same coordiantes as points.
  this.Visibility = true; // An easy way to turn off a shape (with removing it from the shapeList).
  this.Active = false;
  this.ActiveColor = [1.0, 1.0, 0.0];
  // Playing around with layering.  The anchor is being obscured by the text.
  this.ZOffset = 0.1;
  };

Shape.prototype.destructor=function() {
  // Get rid of the buffers?
}

Shape.prototype.Draw = function (view) {
  if ( ! this.Visibility) {
    return;
  }
  if (this.Matrix == undefined) {
    this.UpdateBuffers();
  }

  if (GL) {
    // Lets use the camera to change coordinate system to pixels.
    // TODO: Put this camera in the view or viewer to avoid creating one each render.
    var camMatrix = mat4.create();
    mat4.identity(camMatrix);
    if (this.FixedSize) {
      var viewFrontZ = view.Camera.ZRange[0]+0.01;
      // This camera matric changes pixel/ screen coordinate sytem to
      // view [-1,1],[-1,1],z
      camMatrix[0] = 2.0 / view.Viewport[2];
      camMatrix[12] = -1.0;
      camMatrix[5] = 2.0 / view.Viewport[3];
      camMatrix[13] = -1.0;
      camMatrix[14] = viewFrontZ; // In front of tiles in this view
    }

    // The actor matrix that rotates to orientation and shift (0,0) to origin.
    // Rotate based on ivar orientation.
    var theta = this.Orientation * 3.1415926536 / 180.0;
    this.Matrix[0] =  Math.cos(theta);
    this.Matrix[1] =  Math.sin(theta);
    this.Matrix[4] = -Math.sin(theta);
    this.Matrix[5] =  Math.cos(theta);
    // Place the origin of the shape.
    x = this.Origin[0];
    y = this.Origin[1];
    if (this.FixedSize) {
      // For fixed size, translation must be in view/pixel coordinates.
      // First transform the world to view.
      var m = view.Camera.Matrix;
      var x = (this.Origin[0]*m[0] + this.Origin[1]*m[4] + m[12])/m[15];
      var y = (this.Origin[0]*m[1] + this.Origin[1]*m[5] + m[13])/m[15];
      // convert view to pixels (view coordinate ssytem).
      x = view.Viewport[2]*(0.5*(x+1.0));
      y = view.Viewport[3]*(0.5*(y+1.0));
    }
    // Translate to place the origin.
    this.Matrix[12] = x;
    this.Matrix[13] = y;
    this.Matrix[14] = this.ZOffset;

    var program = polyProgram;

    GL.useProgram(program);
    GL.disable(GL.BLEND);
    GL.enable(GL.DEPTH_TEST);

    // This does not work.
    // I will need to make thick lines with polygons.
    //GL.lineWidth(5);

    // These are the same for every tile.
    // Vertex points (shifted by tiles matrix)
    GL.bindBuffer(GL.ARRAY_BUFFER, this.VertexPositionBuffer);
    // Needed for outline ??? For some reason, DrawOutline did not work
    // without this call first.
    GL.vertexAttribPointer(program.vertexPositionAttribute,
                           this.VertexPositionBuffer.itemSize,
                           GL.FLOAT, false, 0, 0);     // Texture coordinates
    // Local view.
    GL.viewport(view.Viewport[0], view.Viewport[1],
                view.Viewport[2], view.Viewport[3]);

    GL.uniformMatrix4fv(program.mvMatrixUniform, false, this.Matrix);
    if (this.FixedSize) {
      GL.uniformMatrix4fv(program.pMatrixUniform, false, camMatrix);
    } else {
      // Use main views camera to convert world to view.
      GL.uniformMatrix4fv(program.pMatrixUniform, false, view.Camera.Matrix);
    }

    // Fill color
    if (this.FillColor != undefined) {
      if (this.Active) {
        GL.uniform3f(program.colorUniform, this.ActiveColor[0],
                     this.ActiveColor[1], this.ActiveColor[2]);
      } else {
        GL.uniform3f(program.colorUniform, this.FillColor[0],
                     this.FillColor[1], this.FillColor[2]);
      }
      // Cell Connectivity
      GL.bindBuffer(GL.ELEMENT_ARRAY_BUFFER, this.CellBuffer);

      GL.drawElements(GL.TRIANGLES, this.CellBuffer.numItems,
                      GL.UNSIGNED_SHORT,0);
    }

    if (this.OutlineColor != undefined) {
      if (this.Active) {
        GL.uniform3f(program.colorUniform, this.ActiveColor[0],
                     this.ActiveColor[1], this.ActiveColor[2]);
      } else {
        GL.uniform3f(program.colorUniform, this.OutlineColor[0],
                     this.OutlineColor[1], this.OutlineColor[2]);
      }

      if (this.LineWidth == 0) {
        if (this.WireFrame) {
          GL.bindBuffer(GL.ELEMENT_ARRAY_BUFFER, this.CellBuffer);
          GL.drawElements(GL.LINE_LOOP, this.CellBuffer.numItems,
                      GL.UNSIGNED_SHORT,0);
        } else {
          // Outline. This only works for polylines
          GL.drawArrays(GL.LINE_STRIP, 0, this.VertexPositionBuffer.numItems);
        }
      } else {
        // Cell Connectivity
        GL.bindBuffer(GL.ELEMENT_ARRAY_BUFFER, this.LineCellBuffer);
        GL.drawElements(GL.TRIANGLES, this.LineCellBuffer.numItems,
                        GL.UNSIGNED_SHORT,0);
      }
    }
  } else { // 2d Canvas -----------------------------------------------
    view.Context2d.save();
    // Flip y axis
    view.Context2d.setTransform(1,0,0,-1,0,view.Viewport[3]);
    var theta = this.Orientation * 3.1415926536 / 180.0;
    this.Matrix[0] =  Math.cos(theta);
    this.Matrix[1] =  Math.sin(theta);
    this.Matrix[4] = -Math.sin(theta);
    this.Matrix[5] =  Math.cos(theta);
    // Place the origin of the shape.
    x = this.Origin[0];
    y = this.Origin[1];
    var scale = 1.0;
    if ( ! this.FixedSize) {
      // World need to be drawn in view coordinate system so the
      scale = view.Viewport[3] / view.Camera.GetHeight();
    }
    // First transform the origin-world to view.
    var m = view.Camera.Matrix;
    var x = (this.Origin[0]*m[0] + this.Origin[1]*m[4] + m[12])/m[15];
    var y = (this.Origin[0]*m[1] + this.Origin[1]*m[5] + m[13])/m[15];
    // convert origin-view to pixels (view coordinate system).
    x = view.Viewport[2]*(0.5*(x+1.0));
    y = view.Viewport[3]*(0.5*(y+1.0));
    view.Context2d.transform(this.Matrix[0],this.Matrix[1],this.Matrix[4],this.Matrix[5],x,y);
    view.Context2d.beginPath();
    view.Context2d.moveTo(this.PointBuffer[0]*scale,this.PointBuffer[1]*scale);
    var i = 3;
    while ( i < this.PointBuffer.length ) {
      view.Context2d.lineTo(this.PointBuffer[i]*scale,this.PointBuffer[i+1]*scale);
      i += 3;
    }

    if (this.OutlineColor != undefined) {
      var width = this.LineWidth * scale;
      if (width == 0) {
        width = 1;
      }
      view.Context2d.lineWidth = width;
      if (this.Active) {
        view.Context2d.strokeStyle=ConvertColorToHex(this.ActiveColor);
      } else {
        view.Context2d.strokeStyle=ConvertColorToHex(this.OutlineColor);
      }
      view.Context2d.stroke();
    }

    if (this.FillColor != undefined) {
      if (this.Active) {
        view.Context2d.fillStyle=ConvertColorToHex(this.ActiveColor);
      } else {
        view.Context2d.fillStyle=ConvertColorToHex(this.FillColor);
      }
      view.Context2d.fill();
    }

    view.Context2d.restore();
  }
}

// Invert the fill color.
Shape.prototype.ChooseOutlineColor = function () {
  if (this.FillColor) {
    this.OutlineColor = [1.0-this.FillColor[0],
                         1.0-this.FillColor[1],
                         1.0-this.FillColor[2]];

  }
}

Shape.prototype.SetOutlineColor = function (c) {
  this.OutlineColor = ConvertColor(c);
}

Shape.prototype.SetFillColor = function (c) {
  this.FillColor = ConvertColor(c);
}

Shape.prototype.HandleMouseMove = function(event, dx,dy) {
  // superclass does nothing
  return false;
}

//Shape.prototype.UpdateBuffers = function() {
    //    // The superclass does not implement this method.
//}

Shape.prototype.IntersectPointLine = function(pt, end0, end1, thickness) {
  // make end0 the origin.
  var x = pt[0] - end0[0];
  var y = pt[1] - end0[1];
  var vx = end1[0] - end0[0];
  var vy = end1[1] - end0[1];

  // Rotate so the edge lies on the x axis.
  var length = Math.sqrt(vx*vx + vy*vy); // Avoid atan2 ... with clever use of complex numbers.
  vx = vx/length;
  vy = -vy/length;
  var newX = (x*vx - y*vy);
  var newY = (x*vy + y*vx);

  if (newX >= 0.0 && newX <= length) {
    if (Math.abs(newY) < (thickness *0.5)) {
      return true;
    }
  return false;
  }
}

<|endoftext|>


<commit_before>const log = require('sepal/log').getLogger('session')
const pty = require('node-pty')
const fs = require('fs')

const {homeDir, sshScriptPath} = require('./config')

const sessions = {}

const getUsername = sepalUser => {
    if (sepalUser) {
        const username = sepalUser.username
        if (username) {
            return username
        } else {
            throw Error('Missing username in sepal-user header')
        }
    } else {
        throw Error('Missing sepal-user header in request')
    }
}

const getKeyFile = username => {
    const keyFile = `${homeDir}/${username}/.ssh/id_rsa`
    if (fs.existsSync(keyFile)) {
        return keyFile
    } else {
        throw Error(`Missing keyfile: ${keyFile}`)
    }
}

const getTempKeyFile = (username, id) => {
    const tempKeyFile = `/tmp/${username}-${id}.key`
    if (!fs.existsSync(tempKeyFile)) {
        return tempKeyFile
    } else {
        throw Error(`Already existing temporary keyFile: ${tempKeyFile}`)
    }
}

const getTerminal = ({id, username, keyFile, tempKeyFile, cols, rows}) => {
    if (!fs.existsSync(sshScriptPath)) {
        throw Error(`Missing ssh script file: ${sshScriptPath}`)
    }

    log.info({sshScriptPath, username, keyFile, tempKeyFile})
    // const terminal = pty.spawn(sshScriptPath, [username, keyFile, tempKeyFile], {
    const terminal = pty.spawn(sshScriptPath, ['sepal', keyFile, tempKeyFile], {
        name: 'xterm-color',
        cols: cols || 80,
        rows: rows || 24,
        cwd: process.env.PWD,
        env: process.env
    })
    log.info(`Created session: ${id}, terminal PID: ${terminal.pid}`)
    return terminal
}

const create = (id, req) => {
    const cols = parseInt(req.query.cols)
    const rows = parseInt(req.query.rows)
    const sepalUser = JSON.parse(req.headers['sepal-user'])

    const username = getUsername(sepalUser)
    const keyFile = getKeyFile(username)
    const tempKeyFile = getTempKeyFile(username, id)
    const terminal = getTerminal({id, username, keyFile, tempKeyFile, cols, rows})

    const session = {
        id,
        terminal,
        tempKeyFile
    }

    return session
}

const get = req => {
    const id = req.params.sessionId
    if (!sessions[id]) {
        sessions[id] = create(id, req)
    }
    return sessions[id]
}

const remove = id => {
    if (sessions[id]) {
        delete sessions[id]
        log.info(`Removed session: ${id}`)
    } else {
        log.warn(`Cannot remove session: ${id}`)
    }
}

module.exports = {get, remove}
<commit_msg>Fixed terminal.<commit_after>const log = require('sepal/log').getLogger('session')
const pty = require('node-pty')
const fs = require('fs')

const {homeDir, sshScriptPath} = require('./config')

const sessions = {}

const getUsername = sepalUser => {
    if (sepalUser) {
        const username = sepalUser.username
        if (username) {
            return username
        } else {
            throw Error('Missing username in sepal-user header')
        }
    } else {
        throw Error('Missing sepal-user header in request')
    }
}

const getKeyFile = username => {
    const keyFile = `${homeDir}/${username}/.ssh/id_rsa`
    return keyFile
}

const getTempKeyFile = (username, id) => {
    const tempKeyFile = `/tmp/${username}-${id}.key`
    if (!fs.existsSync(tempKeyFile)) {
        return tempKeyFile
    } else {
        throw Error(`Already existing temporary keyFile: ${tempKeyFile}`)
    }
}

const getTerminal = ({id, username, keyFile, tempKeyFile, cols, rows}) => {
    if (!fs.existsSync(sshScriptPath)) {
        throw Error(`Missing ssh script file: ${sshScriptPath}`)
    }

    const terminal = pty.spawn(sshScriptPath, [username, keyFile, tempKeyFile], {
        name: 'xterm-color',
        cols: cols || 80,
        rows: rows || 24,
        cwd: process.env.PWD,
        env: process.env
    })
    log.info(`Created session: ${id}, terminal PID: ${terminal.pid}`)
    return terminal
}

const create = (id, req) => {
    const cols = parseInt(req.query.cols)
    const rows = parseInt(req.query.rows)
    const sepalUser = JSON.parse(req.headers['sepal-user'])

    const username = getUsername(sepalUser)
    const keyFile = getKeyFile(username)
    const tempKeyFile = getTempKeyFile(username, id)
    const terminal = getTerminal({id, username, keyFile, tempKeyFile, cols, rows})

    const session = {
        id,
        terminal,
        tempKeyFile
    }

    return session
}

const get = req => {
    const id = req.params.sessionId
    if (!sessions[id]) {
        sessions[id] = create(id, req)
    }
    return sessions[id]
}

const remove = id => {
    if (sessions[id]) {
        delete sessions[id]
        log.info(`Removed session: ${id}`)
    } else {
        log.warn(`Cannot remove session: ${id}`)
    }
}

module.exports = {get, remove}
<|endoftext|>