<commit_before>'use strict';

/* Directives */

angular.module('odyssey.directives', []).
  directive('appVersion', ['version', function(version) {
    return function(scope, elm, attrs) {
      elm.text(version);
    };
  }]).
  directive('googleplace', ['currentCity', '$location', function(currentCity, $location) {
    return {
        link: function(scope, element, attrs) {
                    var options = {
                        types: ['(cities)'],
                        componentRestrictions: {}
                    };
                    scope.gPlace = new google.maps.places.Autocomplete(element[0], options);
                    google.maps.event.addListener(scope.gPlace, 'place_changed', function() {
    		                var place = scope.gPlace.getPlace();
                            console.log(place);
    		                var current_city = {
    		                 	name: place.name,
    		                	formatted_address: place.formatted_address,
    		                	location: {
    		                		lat: place.geometry.location.lb,
    		                		lng: place.geometry.location.mb
    		                	}
    		                }
    		                currentCity.setProperty(current_city);

                        var form = document.createElement("form");
                        form.setAttribute("method", "post");
                        form.setAttribute("action", "/trips");

                        var hiddenField = document.createElement("input");
                        hiddenField.setAttribute("type", "hidden");
                        hiddenField.setAttribute("name", "trip[name]");
                        hiddenField.setAttribute("value", place.name);
                        form.appendChild(hiddenField);

                        var hiddenField = document.createElement("input");
                        hiddenField.setAttribute("type", "hidden");
                        hiddenField.setAttribute("name", "trip[current_lat]");
                        hiddenField.setAttribute("value", place.geometry.location.lb);
                        form.appendChild(hiddenField);

                        var hiddenField = document.createElement("input");
                        hiddenField.setAttribute("type", "hidden");
                        hiddenField.setAttribute("name", "trip[current_lng]");
                        hiddenField.setAttribute("value", place.geometry.location.mb);
                        form.appendChild(hiddenField);

                        var hiddenField = document.createElement("input");
                        hiddenField.setAttribute("type", "hidden");
                        hiddenField.setAttribute("name", "formatted_address");
                        hiddenField.setAttribute("value", place.formatted_address);
                        form.appendChild(hiddenField);

                        document.body.appendChild(form);
                        form.submit();

    		            });
                }

    }
}]);
<commit_msg>finally fixed the googlemaps changing lat and lng variable names bug<commit_after>'use strict';

/* Directives */

angular.module('odyssey.directives', []).
  directive('appVersion', ['version', function(version) {
    return function(scope, elm, attrs) {
      elm.text(version);
    };
  }]).
  directive('googleplace', ['currentCity', '$location', function(currentCity, $location) {
    return {
        link: function(scope, element, attrs) {
                    var options = {
                        types: ['(cities)'],
                        componentRestrictions: {}
                    };
                    scope.gPlace = new google.maps.places.Autocomplete(element[0], options);
                    google.maps.event.addListener(scope.gPlace, 'place_changed', function() {
    		                var place = scope.gPlace.getPlace();
                            console.log(place);
                            console.log(place.geometry.location.lat());
    		                var current_city = {
    		                 	name: place.name,
    		                	formatted_address: place.formatted_address,
    		                	location: {
    		                		lat: place.geometry.location.lat(),
    		                		lng: place.geometry.location.lng()
    		                	}
    		                }
    		                currentCity.setProperty(current_city);

                        var form = document.createElement("form");
                        form.setAttribute("method", "post");
                        form.setAttribute("action", "/trips");

                        var hiddenField = document.createElement("input");
                        hiddenField.setAttribute("type", "hidden");
                        hiddenField.setAttribute("name", "trip[name]");
                        hiddenField.setAttribute("value", place.name);
                        form.appendChild(hiddenField);

                        var hiddenField = document.createElement("input");
                        hiddenField.setAttribute("type", "hidden");
                        hiddenField.setAttribute("name", "trip[current_lat]");
                        hiddenField.setAttribute("value", place.geometry.location.lat());
                        form.appendChild(hiddenField);

                        var hiddenField = document.createElement("input");
                        hiddenField.setAttribute("type", "hidden");
                        hiddenField.setAttribute("name", "trip[current_lng]");
                        hiddenField.setAttribute("value", place.geometry.location.lng());
                        form.appendChild(hiddenField);

                        var hiddenField = document.createElement("input");
                        hiddenField.setAttribute("type", "hidden");
                        hiddenField.setAttribute("name", "formatted_address");
                        hiddenField.setAttribute("value", place.formatted_address);
                        form.appendChild(hiddenField);

                        document.body.appendChild(form);
                        form.submit();

    		            });
                }

    }
}]);
<|endoftext|>


<commit_before>// @flow
import React, {Component} from 'react'
import {TextInput, Animated} from 'react-native'
import {Box, Text, Icon, ClickableBox} from '../../common-adapters'
import {globalColors, globalMargins, globalStyles} from '../../styles'
import IconOrAvatar from '../icon-or-avatar'
import {followingStateToStyle} from '../shared'
import {getStyle as getTextStyle} from '../../common-adapters/text'

import type {UserDetails, Props} from './'

type UserItemProps = UserDetails & {onRemoveUser: (username: string) => void}
type UserItemState = {isSelected: boolean, selectAnim: Animated.Value}

class UserItem extends Component<void, UserItemProps, UserItemState> {
  state = {
    isSelected: false,
    selectAnim: new Animated.Value(0),
  }

  _onRemoveUser = () => {
    this.props.onRemoveUser(this.props.username)
  }

  _onSelect = () => {
    Animated.timing(this.state.selectAnim, {toValue: 1, duration: 100}).start()
    this.setState({isSelected: true})
  }

  _onDeselect = () => {
    Animated.timing(this.state.selectAnim, {toValue: 0, duration: 100}).start()
    this.setState({isSelected: false})
  }

  _onChangeText = (text: string) => {
    if (!text.length) {
      this._onDeselect()
      this._onRemoveUser()
    }
  }

  render() {
    const {followingState, icon, service, username} = this.props
    const {isSelected, selectAnim} = this.state

    const usernameStyle = followingStateToStyle(followingState)
    return (
      <Box style={{...globalStyles.flexBoxRow, marginRight: globalMargins.xtiny}}>
        <ClickableBox feedback={false} onClick={this._onSelect}>
          <Animated.View
            style={{
              ..._pillStyle,
              backgroundColor: selectAnim.interpolate({
                inputRange: [0, 1],
                outputRange: [globalColors.white_0, globalColors.blue],
              }),
            }}
          >
            <IconOrAvatar icon={icon} service={service} username={username} avatarSize={16} />
            <Animated.Text
              style={{
                ..._pillTextStyle,
                ...usernameStyle,
                ...globalStyles.fontSemibold,
                color: selectAnim.interpolate({
                  inputRange: [0, 1],
                  outputRange: [usernameStyle.color, globalColors.white],
                }),
                marginLeft: 3,
              }}
            >
              {username}
            </Animated.Text>
          </Animated.View>
        </ClickableBox>
        <Text
          type="BodySemibold"
          style={{
            ..._pillTextStyle,
            color: globalColors.black_20,
          }}
        >
          ,
        </Text>
        {/* We can't listen to keyboard events in RN Android, so instead we
            create an offscreen text input with a single space character and
            observe it being removed when the value changes. */}
        {isSelected &&
          <TextInput
            autoFocus={true}
            onBlur={this._onDeselect}
            onChangeText={this._onChangeText}
            value=" "
            style={{position: 'absolute', left: -9999}}
          />}
      </Box>
    )
  }
}

type State = {isFocused: boolean}

class UserInput extends Component<void, Props, State> {
  _textInput: TextInput

  state = {
    isFocused: false,
  }

  _focusInput = () => {
    this._textInput.focus()
  }

  _onFocus = () => {
    this.setState({isFocused: true})
  }

  _onBlur = () => {
    this.setState({isFocused: false})
  }

  _onRemoveUser = (username: string) => {
    this.props.onRemoveUser(username)
    this._focusInput()
  }

  render() {
    const {placeholder, userItems, usernameText, onChangeText, onClickAddButton} = this.props

    const showAddButton = !!userItems.length && !usernameText.length
    return (
      <ClickableBox feedback={false} onClick={this._focusInput}>
        <Box style={{...globalStyles.flexBoxRow, alignItems: 'center', flexWrap: 'wrap'}}>
          {userItems.map(item => (
            <UserItem {...item} onRemoveUser={this._onRemoveUser} key={item.username} />
          ))}
          <Box
            style={{
              ...globalStyles.flexBoxRow,
              height: 24,
              alignItems: 'center',
              flexGrow: 1,
              minWidth: 50,
            }}
          >
            <TextInput
              ref={el => {
                this._textInput = el
              }}
              onFocus={this._onFocus}
              onBlur={this._onBlur}
              style={{
                ..._inputStyle,
                ...(showAddButton ? {width: this.state.isFocused ? 10 : 0} : {flexGrow: 1}),
              }}
              placeholder={userItems.length ? '' : placeholder}
              value={usernameText}
              onChangeText={onChangeText}
            />
            {showAddButton &&
              <Icon
                onClick={onClickAddButton}
                type="iconfont-add"
                style={{
                  fontSize: 16,
                  height: 16,
                  color: globalColors.blue,
                }}
              />}
          </Box>
        </Box>
      </ClickableBox>
    )
  }
}

const _pillStyle = {
  ...globalStyles.flexBoxRow,
  alignItems: 'center',
  height: 24,
  paddingTop: 2,
  paddingBottom: 2,
  paddingLeft: 1,
  paddingRight: 1,
  borderRadius: 2,
}

const _pillTextStyle = {
  fontSize: 14,
  lineHeight: 20,
  height: 22,
}

const _inputStyle = {
  ...getTextStyle('Body'),
  fontSize: 14,
  color: globalColors.black_75,
  lineHeight: 20,
  height: 22,
  paddingTop: 2,
  paddingBottom: 2,
}

export default UserInput
<commit_msg>Use id for remove user and key<commit_after>// @flow
import React, {Component} from 'react'
import {TextInput, Animated} from 'react-native'
import {Box, Text, Icon, ClickableBox} from '../../common-adapters'
import {globalColors, globalMargins, globalStyles} from '../../styles'
import IconOrAvatar from '../icon-or-avatar'
import {followingStateToStyle} from '../shared'
import {getStyle as getTextStyle} from '../../common-adapters/text'

import type {UserDetails, Props} from './'

type UserItemProps = UserDetails & {onRemoveUser: (id: string) => void}
type UserItemState = {isSelected: boolean, selectAnim: Animated.Value}

class UserItem extends Component<void, UserItemProps, UserItemState> {
  state = {
    isSelected: false,
    selectAnim: new Animated.Value(0),
  }

  _onRemoveUser = () => {
    this.props.onRemoveUser(this.props.id)
  }

  _onSelect = () => {
    Animated.timing(this.state.selectAnim, {toValue: 1, duration: 100}).start()
    this.setState({isSelected: true})
  }

  _onDeselect = () => {
    Animated.timing(this.state.selectAnim, {toValue: 0, duration: 100}).start()
    this.setState({isSelected: false})
  }

  _onChangeText = (text: string) => {
    if (!text.length) {
      this._onDeselect()
      this._onRemoveUser()
    }
  }

  render() {
    const {followingState, icon, service, username} = this.props
    const {isSelected, selectAnim} = this.state

    const usernameStyle = followingStateToStyle(followingState)
    return (
      <Box style={{...globalStyles.flexBoxRow, marginRight: globalMargins.xtiny}}>
        <ClickableBox feedback={false} onClick={this._onSelect}>
          <Animated.View
            style={{
              ..._pillStyle,
              backgroundColor: selectAnim.interpolate({
                inputRange: [0, 1],
                outputRange: [globalColors.white_0, globalColors.blue],
              }),
            }}
          >
            <IconOrAvatar icon={icon} service={service} username={username} avatarSize={16} />
            <Animated.Text
              style={{
                ..._pillTextStyle,
                ...usernameStyle,
                ...globalStyles.fontSemibold,
                color: selectAnim.interpolate({
                  inputRange: [0, 1],
                  outputRange: [usernameStyle.color, globalColors.white],
                }),
                marginLeft: 3,
              }}
            >
              {username}
            </Animated.Text>
          </Animated.View>
        </ClickableBox>
        <Text
          type="BodySemibold"
          style={{
            ..._pillTextStyle,
            color: globalColors.black_20,
          }}
        >
          ,
        </Text>
        {/* We can't listen to keyboard events in RN Android, so instead we
            create an offscreen text input with a single space character and
            observe it being removed when the value changes. */}
        {isSelected &&
          <TextInput
            autoFocus={true}
            onBlur={this._onDeselect}
            onChangeText={this._onChangeText}
            value=" "
            style={{position: 'absolute', left: -9999}}
          />}
      </Box>
    )
  }
}

type State = {isFocused: boolean}

class UserInput extends Component<void, Props, State> {
  _textInput: TextInput

  state = {
    isFocused: false,
  }

  _focusInput = () => {
    this._textInput.focus()
  }

  _onFocus = () => {
    this.setState({isFocused: true})
  }

  _onBlur = () => {
    this.setState({isFocused: false})
  }

  _onRemoveUser = (username: string) => {
    this.props.onRemoveUser(username)
    this._focusInput()
  }

  render() {
    const {placeholder, userItems, usernameText, onChangeText, onClickAddButton} = this.props

    const showAddButton = !!userItems.length && !usernameText.length
    return (
      <ClickableBox feedback={false} onClick={this._focusInput}>
        <Box style={{...globalStyles.flexBoxRow, alignItems: 'center', flexWrap: 'wrap'}}>
          {userItems.map(item => <UserItem {...item} onRemoveUser={this._onRemoveUser} key={item.id} />)}
          <Box
            style={{
              ...globalStyles.flexBoxRow,
              height: 24,
              alignItems: 'center',
              flexGrow: 1,
              minWidth: 50,
            }}
          >
            <TextInput
              ref={el => {
                this._textInput = el
              }}
              onFocus={this._onFocus}
              onBlur={this._onBlur}
              style={{
                ..._inputStyle,
                ...(showAddButton ? {width: this.state.isFocused ? 10 : 0} : {flexGrow: 1}),
              }}
              placeholder={userItems.length ? '' : placeholder}
              value={usernameText}
              onChangeText={onChangeText}
            />
            {showAddButton &&
              <Icon
                onClick={onClickAddButton}
                type="iconfont-add"
                style={{
                  fontSize: 16,
                  height: 16,
                  color: globalColors.blue,
                }}
              />}
          </Box>
        </Box>
      </ClickableBox>
    )
  }
}

const _pillStyle = {
  ...globalStyles.flexBoxRow,
  alignItems: 'center',
  height: 24,
  paddingTop: 2,
  paddingBottom: 2,
  paddingLeft: 1,
  paddingRight: 1,
  borderRadius: 2,
}

const _pillTextStyle = {
  fontSize: 14,
  lineHeight: 20,
  height: 22,
}

const _inputStyle = {
  ...getTextStyle('Body'),
  fontSize: 14,
  color: globalColors.black_75,
  lineHeight: 20,
  height: 22,
  paddingTop: 2,
  paddingBottom: 2,
}

export default UserInput
<|endoftext|>


<commit_before>'use strict';

const registrationRegExp = /^(\+|-)\s*((\s*@[\S]+)*)$/;

class Registration {
	constructor( context, timeToLive ) {
		// Default time to live is 8 hrs
		this.timeToLive = timeToLive ? timeToLive : 8 * 60 * 60 * 1000;
		this.context = context;
		this.registered = [];
	}

	handleRequest( request, asyncResponse ) {
		if ( this.cleanRegistered() ) {
			asyncResponse( request.response_url, 'Old users were removed from queue.' );
		}

		const registered = this.registered;
		const registrationValues = registrationRegExp.exec( request.resolvedText );
		const context = this.context;

		if ( !registrationValues ) {
			return;
		}

		const users = registrationValues[ 2 ].split( '@' )
			.filter( user => user.length )
			.map( ( user ) => user.trim() );

		if ( !users.length ) {
			users.push( request.user_name );
		}

		const isAdd = registrationValues[ 1 ] == '+';

		for ( let user of users ) {
			if ( isAdd ) {
				add( user );
			} else {
				remove( user );
			}
		}

		const usersMessage = ( users.map( user => '@' + user ).join( ', ' ) );

		return {
			'response_type': 'ephemeral',
			'text': `${usersMessage} have been ${( isAdd ? 'added to' : 'removed from' )} the next match.`
		};

		function add( user ) {
			// Add the player only if he/she is not registered already.
			// We could return if user in in registered pool already but it could be abused to check if someone else is already registered.
			if ( hasUser( registered, user ) ) {
				asyncResponse( request.response_url, 'Oops! Some users already registered and will not be re-added!', 'ephemeral' );
				return;
			}

			registered.push( { name: user, date: new Date() } );

			const playersCount = registered.length % 4;

			if ( playersCount ) {
				asyncResponse( request.response_url, 'A new player joined the next match! ' +
					'Waiting for the next *' + ( 4 - playersCount ) + '*!' );
			} else {
				const players = registered.splice( -4 );
				players.sort( ( playerA, playerB ) => context.rank.getPlayer( playerA.name ).score -
				context.rank.getPlayer( playerB.name ).score );
				const expected = context.rank.getExpected( players[ 0 ], players[ 3 ], players[ 1 ], players[ 2 ] );

				asyncResponse( request.response_url, ':fire: @' + players[ 0 ].name + ' @' + players[ 3 ].name +
					' (' + expected.red + ' : ' + expected.blue + ')' +
					' @' + players[ 1 ].name + ' @' + players[ 2 ].name );
			}
		}

		function remove( user ) {
			const index = registered.map( ( registeredUser ) => registeredUser.name ).lastIndexOf( user );

			if ( index > -1 ) {
				registered.splice( index, 1 );

				const playersCount = registered.length % 4;

				asyncResponse( request.response_url, 'User removed from the match. :chicken: ' +
					'Now we need ' + ( 4 - playersCount ) + ' players.' );
			}
		}

		function hasUser( registered, user ) {
			return registered.map( ( registeredUser ) => registeredUser.name ).indexOf( user ) !== -1;
		}
	}

	cleanRegistered() {
		const now = new Date();

		const registeredBefore = this.registered.length;

		this.registered = this.registered.filter( ( registeredUser ) => {
			return ( now.getTime() - registeredUser.date.getTime() ) < this.timeToLive;
		} );

		return this.registered.length !== registeredBefore;
	}
}

module.exports = Registration;
<commit_msg>Fix expected on registration.<commit_after>'use strict';

const registrationRegExp = /^(\+|-)\s*((\s*@[\S]+)*)$/;

class Registration {
	constructor( context, timeToLive ) {
		// Default time to live is 8 hrs
		this.timeToLive = timeToLive ? timeToLive : 8 * 60 * 60 * 1000;
		this.context = context;
		this.registered = [];
	}

	handleRequest( request, asyncResponse ) {
		if ( this.cleanRegistered() ) {
			asyncResponse( request.response_url, 'Old users were removed from queue.' );
		}

		const registered = this.registered;
		const registrationValues = registrationRegExp.exec( request.resolvedText );
		const context = this.context;

		if ( !registrationValues ) {
			return;
		}

		const users = registrationValues[ 2 ].split( '@' )
			.filter( user => user.length )
			.map( ( user ) => user.trim() );

		if ( !users.length ) {
			users.push( request.user_name );
		}

		const isAdd = registrationValues[ 1 ] == '+';

		for ( let user of users ) {
			if ( isAdd ) {
				add( user );
			} else {
				remove( user );
			}
		}

		const usersMessage = ( users.map( user => '@' + user ).join( ', ' ) );

		return {
			'response_type': 'ephemeral',
			'text': `${usersMessage} have been ${( isAdd ? 'added to' : 'removed from' )} the next match.`
		};

		function add( user ) {
			// Add the player only if he/she is not registered already.
			// We could return if user in in registered pool already but it could be abused to check if someone else is already registered.
			if ( hasUser( registered, user ) ) {
				asyncResponse( request.response_url, 'Oops! Some users already registered and will not be re-added!', 'ephemeral' );
				return;
			}

			registered.push( { name: user, date: new Date() } );

			const playersCount = registered.length % 4;

			if ( playersCount ) {
				asyncResponse( request.response_url, 'A new player joined the next match! ' +
					'Waiting for the next *' + ( 4 - playersCount ) + '*!' );
			} else {
				const players = registered.splice( -4 );

				players.sort( ( playerA, playerB ) => {
					return context.rank.getPlayer( playerA.name ).score - context.rank.getPlayer( playerB.name ).score;
				} );

				const expected = context.rank.getExpected( players[ 0 ].name, players[ 3 ].name, players[ 1 ].name, players[ 2 ].name );

				asyncResponse( request.response_url, ':fire: @' + players[ 0 ].name + ' @' + players[ 3 ].name +
					' (' + expected.red + ' : ' + expected.blue + ')' +
					' @' + players[ 1 ].name + ' @' + players[ 2 ].name );
			}
		}

		function remove( user ) {
			const index = registered.map( ( registeredUser ) => registeredUser.name ).lastIndexOf( user );

			if ( index > -1 ) {
				registered.splice( index, 1 );

				const playersCount = registered.length % 4;

				asyncResponse( request.response_url, 'User removed from the match. :chicken: ' +
					'Now we need ' + ( 4 - playersCount ) + ' players.' );
			}
		}

		function hasUser( registered, user ) {
			return registered.map( ( registeredUser ) => registeredUser.name ).indexOf( user ) !== -1;
		}
	}

	cleanRegistered() {
		const now = new Date();

		const registeredBefore = this.registered.length;

		this.registered = this.registered.filter( ( registeredUser ) => {
			return ( now.getTime() - registeredUser.date.getTime() ) < this.timeToLive;
		} );

		return this.registered.length !== registeredBefore;
	}
}

module.exports = Registration;
<|endoftext|>


<commit_before>define(["require", "js/html/HtmlElement", "js/ui/ContentPlaceHolder", "js/core/Module", "underscore", "js/conf/ModuleConfiguration", "flow"],
    function (require, HtmlElement, ContentPlaceHolder, Module, _, ModuleConfiguration, flow) {
        var ModuleLoader = HtmlElement.inherit("js.core.ModuleLoader", {

            $classAttributes: ['router', 'currentModuleName', 'state'],
            defaults: {
                currentModuleName:  null,
                tagName: 'div',
                componentClass: "module-loader",
                state: null
            },

            ctor: function (attributes) {
                this.callBase();
                this.$modules = {};
                this.$moduleCache = {};
            },

            _initializationComplete: function () {
                this.callBase();

                for (var i = 0; i < this.$configurations.length; i++) {
                    var config = this.$configurations[i];

                    if (config instanceof ModuleConfiguration) {
                        this.addModule(config.$);
                    }
                }
            },

            /***
             * adds a module to the list of known modules
             *
             * @param {js.conf.ModuleConfiguration} module
             */
            addModule: function (module) {
                _.defaults(module, {
                    name: null,
                    moduleClass: null,
                    route: null
                });

                if (!module.name) {
                    throw "module cannot be added: module name is required";
                }

                if (!module.moduleClass) {
                    throw "no moduleClass defined for module";
                }

                if (this.$modules.hasOwnProperty(module.name)) {
                    throw "module with name '" + module.name + "' already registered"
                }

                this.$modules[module.name] = module;

                if (module.route) {
                    if (!this.$.router) {
                        throw "defining modules with routes requires a router instance to be set"
                    }

                    var self = this;
                    this.$.router.addRoute({
                        name: module.name,
                        route: module.route,
                        fn: function (routeContext) {

                            // route triggered -> load module
                            self.loadModule(module, routeContext.callback, routeContext);
                        }.async()
                    });
                }

            },

            _startModule: function (moduleName, moduleInstance, callback, routeContext, cachedInstance) {

                var self = this;
                this.set('currentModuleName', moduleName);

                var contentPlaceHolders = this.getContentPlaceHolders();

                // set content
                for (var i = 0; i < contentPlaceHolders.length; i++) {
                    var contentPlaceHolder = contentPlaceHolders[i];
                    contentPlaceHolder.set("content", moduleInstance.findContent(contentPlaceHolder.$.name));
                }

                var internalCallback = function(err) {

                    self.set('state', err ? 'error' : null);

                    if (callback) {
                        callback(err);
                    }
                };

                // start module
                moduleInstance.start(function (err) {

                    if (err || cachedInstance) {
                        internalCallback(err);
                    } else {
                        if (routeContext) {
                            // fresh instance with maybe new routers -> exec routes for new router
                            var routeExecutionStack = [];

                            for (var i = 0; i < moduleInstance.$routers.length; i++) {
                                routeExecutionStack = routeExecutionStack.concat(moduleInstance.$routers[i].generateRoutingStack(routeContext.fragment));
                            }

                            flow()
                                .seqEach(routeExecutionStack, function(routingFunction, cb) {
                                    routingFunction(cb);
                                })
                                .exec(internalCallback);

                        } else {
                            internalCallback();
                        }

                    }
                }, routeContext);

            },

            /***
             * loads an module instance into the module loader and starts the module
             *
             * @param {js.conf.ModuleConfiguration} module - the module to load
             * @param {Function} [callback] - a callback function which gets invoked after the module is loaded or an error occurred
             * @param {js.core.Router.RouteContext} [routeContext]
             */
            loadModule: function (module, callback, routeContext) {
                if (module.name === this.$.currentModuleName) {
                    // module already shown
                    if (callback) {
                        callback();
                    }
                } else {

                    this._clearContentPlaceHolders();
                    this.$lastModuleName = this.$.currentModuleName;
                    this.set('currentModuleName', null);
                    this.set('state', 'loading');

                    if (this.$moduleCache.hasOwnProperty(module.name)) {
                        this._startModule(module.name, this.$moduleCache[module.name], callback, routeContext, true);
                    } else {

                        var self = this;
                        // load module

                        require([this.$stage.$applicationContext.getFqClassName(module.moduleClass)], function (moduleBaseClass) {
                            var moduleInstance = new moduleBaseClass(null, false, self.$stage, null, null);

                            if (moduleInstance instanceof Module) {
                                moduleInstance._initialize("auto");

                                // cache instance
                                self.$moduleCache[module.name] = moduleInstance;

                                self._startModule(module.name, moduleInstance, callback, routeContext, false);

                            } else {
                                if (callback) {
                                    callback("Module '" + module.moduleClass + "' isn't an instance of js.core.Module");
                                }
                            }

                        }, function(err) {

                            self.set('state', 'error');
                            self.log(err, "warn");

                            if (callback) {
                                callback(err);
                            }
                        });
                    }
                }

            },
            isModuleActive: function (moduleName) {
                return this.$.currentModuleName == moduleName;
            }.onChange('currentModuleName'),

            _clearContentPlaceHolders: function() {

                var contentPlaceHolders = this.getContentPlaceHolders();

                // set content
                for (var i = 0; i < contentPlaceHolders.length; i++) {
                    var contentPlaceHolder = contentPlaceHolders[i];
                    contentPlaceHolder.set("content", null);
                }

            },

            // TODO: remove this method and make the modules bindable

            /***
             * @deprecated will be removed soon
             * @return {Array}
             */
            moduleNames: function(){
                var modules = [], conf;
                for(var i = 0; i < this.$configurations.length; i++){
                    conf = this.$configurations[i];
                    if(conf instanceof ModuleConfiguration){
                        modules.push(conf.$.name);
                    }
                }
                return modules;
            },

            _renderState: function(state, oldState) {
                oldState && this.removeClass(oldState);
                state && this.addClass(state);
            }
        });

        ModuleLoader.findContentPlaceHolders = function (component) {
            var ret = [];

            for (var i = 0; i < component.$children.length; i++) {
                var child = component.$children[i];
                if (child instanceof ContentPlaceHolder) {
                    ret.push(child);
                } else {
                    ret.concat(ModuleLoader.findContentPlaceHolders(child));
                }
            }

            return ret;

        };

        return ModuleLoader;
    });<commit_msg>added garbage collection for IE8<commit_after>define(["require", "js/html/HtmlElement", "js/ui/ContentPlaceHolder", "js/core/Module", "underscore", "js/conf/ModuleConfiguration", "flow"],
    function (require, HtmlElement, ContentPlaceHolder, Module, _, ModuleConfiguration, flow) {
        var ModuleLoader = HtmlElement.inherit("js.core.ModuleLoader", {

            $classAttributes: ['router', 'currentModuleName', 'state'],
            defaults: {
                currentModuleName:  null,
                tagName: 'div',
                componentClass: "module-loader",
                state: null
            },

            ctor: function (attributes) {
                this.callBase();
                this.$modules = {};
                this.$moduleCache = {};
            },

            _initializationComplete: function () {
                this.callBase();

                for (var i = 0; i < this.$configurations.length; i++) {
                    var config = this.$configurations[i];

                    if (config instanceof ModuleConfiguration) {
                        this.addModule(config.$);
                    }
                }
            },

            /***
             * adds a module to the list of known modules
             *
             * @param {js.conf.ModuleConfiguration} module
             */
            addModule: function (module) {
                _.defaults(module, {
                    name: null,
                    moduleClass: null,
                    route: null
                });

                if (!module.name) {
                    throw "module cannot be added: module name is required";
                }

                if (!module.moduleClass) {
                    throw "no moduleClass defined for module";
                }

                if (this.$modules.hasOwnProperty(module.name)) {
                    throw "module with name '" + module.name + "' already registered"
                }

                this.$modules[module.name] = module;

                if (module.route) {
                    if (!this.$.router) {
                        throw "defining modules with routes requires a router instance to be set"
                    }

                    var self = this;
                    this.$.router.addRoute({
                        name: module.name,
                        route: module.route,
                        fn: function (routeContext) {

                            // route triggered -> load module
                            self.loadModule(module, routeContext.callback, routeContext);
                        }.async()
                    });
                }

            },

            _startModule: function (moduleName, moduleInstance, callback, routeContext, cachedInstance) {

                var self = this;
                this.set('currentModuleName', moduleName);

                var contentPlaceHolders = this.getContentPlaceHolders();

                // set content
                for (var i = 0; i < contentPlaceHolders.length; i++) {
                    var contentPlaceHolder = contentPlaceHolders[i];
                    contentPlaceHolder.set("content", moduleInstance.findContent(contentPlaceHolder.$.name));
                }

                var internalCallback = function(err) {

                    self.set('state', err ? 'error' : null);

                    if (callback) {
                        callback(err);
                    }
                };

                // start module
                moduleInstance.start(function (err) {

                    if (err || cachedInstance) {
                        internalCallback(err);
                    } else {
                        if (routeContext) {
                            // fresh instance with maybe new routers -> exec routes for new router
                            var routeExecutionStack = [];

                            for (var i = 0; i < moduleInstance.$routers.length; i++) {
                                routeExecutionStack = routeExecutionStack.concat(moduleInstance.$routers[i].generateRoutingStack(routeContext.fragment));
                            }

                            flow()
                                .seqEach(routeExecutionStack, function(routingFunction, cb) {
                                    routingFunction(cb);
                                })
                                .exec(internalCallback);

                        } else {
                            internalCallback();
                        }

                    }
                }, routeContext);

            },

            /***
             * loads an module instance into the module loader and starts the module
             *
             * @param {js.conf.ModuleConfiguration} module - the module to load
             * @param {Function} [callback] - a callback function which gets invoked after the module is loaded or an error occurred
             * @param {js.core.Router.RouteContext} [routeContext]
             */
            loadModule: function (module, callback, routeContext) {
                if (module.name === this.$.currentModuleName) {
                    // module already shown
                    if (callback) {
                        callback();
                    }
                } else {

                    this._clearContentPlaceHolders();
                    this.$lastModuleName = this.$.currentModuleName;
                    this.set('currentModuleName', null);
                    this.set('state', 'loading');

                    if (this.$moduleCache.hasOwnProperty(module.name)) {
                        this._startModule(module.name, this.$moduleCache[module.name], callback, routeContext, true);
                    } else {

                        var self = this;
                        // load module

                        require([this.$stage.$applicationContext.getFqClassName(module.moduleClass)], function (moduleBaseClass) {
                            var moduleInstance = new moduleBaseClass(null, false, self.$stage, null, null);

                            if (moduleInstance instanceof Module) {
                                if (typeof(CollectGarbage) == "function"){
                                    CollectGarbage();
                                }

                                moduleInstance._initialize("auto");

                                // cache instance
                                self.$moduleCache[module.name] = moduleInstance;

                                self._startModule(module.name, moduleInstance, callback, routeContext, false);

                            } else {
                                if (callback) {
                                    callback("Module '" + module.moduleClass + "' isn't an instance of js.core.Module");
                                }
                            }

                        }, function(err) {

                            self.set('state', 'error');
                            self.log(err, "warn");

                            if (callback) {
                                callback(err);
                            }
                        });
                    }
                }

            },
            isModuleActive: function (moduleName) {
                return this.$.currentModuleName == moduleName;
            }.onChange('currentModuleName'),

            _clearContentPlaceHolders: function() {

                var contentPlaceHolders = this.getContentPlaceHolders();

                // set content
                for (var i = 0; i < contentPlaceHolders.length; i++) {
                    var contentPlaceHolder = contentPlaceHolders[i];
                    contentPlaceHolder.set("content", null);
                }

            },

            // TODO: remove this method and make the modules bindable

            /***
             * @deprecated will be removed soon
             * @return {Array}
             */
            moduleNames: function(){
                var modules = [], conf;
                for(var i = 0; i < this.$configurations.length; i++){
                    conf = this.$configurations[i];
                    if(conf instanceof ModuleConfiguration){
                        modules.push(conf.$.name);
                    }
                }
                return modules;
            },

            _renderState: function(state, oldState) {
                oldState && this.removeClass(oldState);
                state && this.addClass(state);
            }
        });

        ModuleLoader.findContentPlaceHolders = function (component) {
            var ret = [];

            for (var i = 0; i < component.$children.length; i++) {
                var child = component.$children[i];
                if (child instanceof ContentPlaceHolder) {
                    ret.push(child);
                } else {
                    ret.concat(ModuleLoader.findContentPlaceHolders(child));
                }
            }

            return ret;

        };

        return ModuleLoader;
    });<|endoftext|>


<commit_before>const webpack = require('webpack')
const CleanWebpackPlugin = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')

const { StatsWriterPlugin } = require('webpack-stats-plugin');
const fs = require('fs');
const sortBy = require('lodash/sortBy');

const config = require('./webpack.dev.js')

// Source maps suitable for production use
config.devtool = 'cheap-module-source-map'

config.plugins.push(
  new webpack.NoEmitOnErrorsPlugin(),

  new webpack.optimize.UglifyJsPlugin(),

  // Cleans previous build
  new CleanWebpackPlugin([config.output.path], {
    allowExternal: true
  }),

  // Replace index.html with correct base href for production use
  new HtmlWebpackPlugin({
    base: '/ui/service/',
    template: '../client/index.ejs',
    chunks: ['app']
  }),

  new OptimizeCssAssetsWebpackPlugin(),

  // Write out dependencies list for audit purposes
  new StatsWriterPlugin({
    filename: "webpack-modules-manifest.json",
    fields: ["modules"],

    transform(data) {
      // recursively flatten nested modules
      const transformModule  = m  => m.modules ? transformModules(m.modules) : m.name.split("!").pop();
      const transformModules = ms => ms.flatMap(m => transformModule(m));

      var modules = transformModules(data.modules)
      modules = [...new Set(modules)]; // uniq

      // find the package.json file for each module
      modules = modules.filter(m => m.includes("node_modules"));
      var packagePaths = modules.map(m => {
        var match = m.match(/(.*node_modules\/)([^/]+)(\/[^/]+)?/);
        var path = [
          `${match[1]}${match[2]}/package.json`,
          `${match[1]}${match[2]}${match[3]}/package.json`,
        ].find(p => fs.existsSync(p));

        if (path == null) {
          console.warn(`[webpack-manifest] WARN: Unable to find a package.json for ${m}`);
        }

        return path;
      })
      packagePaths = packagePaths.filter(p => p != null);
      packagePaths = [...new Set(packagePaths)]; // uniq

      // extract relevant package data from the package.json
      var packages = packagePaths.map(p => {
        var content = fs.readFileSync(p);
        var pkg = JSON.parse(content);
        return {
          name: pkg.name,
          license: pkg.license,
          version: pkg.version,
          location: p
        }
      });
      packages = sortBy(packages, ['name', 'version']);

      return JSON.stringify(packages, null, 2);
    }
  })
)

module.exports = config
<commit_msg>Include modules manifest<commit_after>const webpack = require('webpack')
const CleanWebpackPlugin = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')

const { StatsWriterPlugin } = require('webpack-stats-plugin');
const fs = require('fs');
const sortBy = require('lodash/sortBy');

const config = require('./webpack.dev.js')

const uniqueWebpackModules = (data) => {
  // recursively flatten nested modules
  const transformModule  = m  => m.modules ? transformModules(m.modules) : m.name.split("!").pop();
  const transformModules = ms => ms.flatMap(m => transformModule(m));

  var modules = transformModules(data.modules);
  modules = [...new Set(modules)]; // uniq

  return modules;
}

const packagesFromModules = (modules) => {
  // find the package.json file for each module
  modules = modules.filter(m => m.includes("node_modules"));
  var packagePaths = modules.map(m => {
    var match = m.match(/(.*node_modules\/)([^/]+)(\/[^/]+)?/);
    var path = [
      `${match[1]}${match[2]}/package.json`,
      `${match[1]}${match[2]}${match[3]}/package.json`,
    ].find(p => fs.existsSync(p));

    if (path == null) {
      console.warn(`[webpack-manifest] WARN: Unable to find a package.json for ${m}`);
    }

    return path;
  })
  packagePaths = packagePaths.filter(p => p != null);
  packagePaths = [...new Set(packagePaths)]; // uniq

  // extract relevant package data from the package.json
  var packages = packagePaths.map(p => {
    var content = fs.readFileSync(p);
    var pkg = JSON.parse(content);
    return {
      name: pkg.name,
      license: pkg.license,
      version: pkg.version,
      location: p
    }
  });
  packages = sortBy(packages, ['name', 'version']);

  return packages;
}

// Source maps suitable for production use
config.devtool = 'cheap-module-source-map'

config.plugins.push(
  new webpack.NoEmitOnErrorsPlugin(),

  new webpack.optimize.UglifyJsPlugin(),

  // Cleans previous build
  new CleanWebpackPlugin([config.output.path], {
    allowExternal: true
  }),

  // Replace index.html with correct base href for production use
  new HtmlWebpackPlugin({
    base: '/ui/service/',
    template: '../client/index.ejs',
    chunks: ['app']
  }),

  new OptimizeCssAssetsWebpackPlugin(),

  // Write out dependent modules list for audit purposes
  new StatsWriterPlugin({
    filename: "webpack_modules_manifest.json",
    fields: ["modules"],

    transform(data) {
      var modules = uniqueWebpackModules(data);
      return JSON.stringify(modules, null, 2);
    }
  }),

  // Write out dependent packages list for audit purposes
  new StatsWriterPlugin({
    filename: "webpack_packages_manifest.json",
    fields: ["modules"],

    transform(data) {
      var modules = uniqueWebpackModules(data);
      var packages = packagesFromModules(modules);
      return JSON.stringify(packages, null, 2);
    }
  })
)

module.exports = config
<|endoftext|>


<commit_before>/*global window */
/*global console */

define([
  './util',
  './platform',
  './store/memory',
  './store/localStorage',
  './store/pending'
], function (util, platform, memoryAdapter, localStorageAdapter, pendingAdapter) {

  "use strict";

  // Namespace: store
  //
  // The store stores data locally. It treats all data as raw nodes, that have *metadata* and *payload*.
  // Where the actual data is stored is determined by the <StorageAdapter> that is being used.

  var logger = util.getLogger('store');

  // foreign nodes are prefixed with a user address
  var userAddressRE = /^[^@]+@[^:]+:\//;

  var events = util.getEventEmitter('error', 'change', 'foreign-change');

  var dataStore;

  // Method: setAdapter
  // Set the storage adapter. See <StorageAdapter> for a description of
  // the required interface.
  function setAdapter(adapter) {
    dataStore = adapter;
    // forward changes from data store (e.g. made in other tabs)
    dataStore.on('change', function(event) {
      if(! util.isDir(event.path)) {
        fireChange('device', event.path, event.oldValue);
      }
    });
  }

  // (function() {
  //   if(typeof(window) !== 'undefined') {
  //     var idb = indexedDbAdapter.detect();
  //     if(idb) {
  //       setAdapter(indexedDbAdapter(idb));
  //     } else if(typeof(window.openDatabase) !== 'undefined') {
  //       setAdapter(webSqlAdapter());
  //     } else if(typeof(window.localStorage) !== 'undefined') {
  //       setAdapter(localStorageAdapter(window.localStorage));
  //     } else {
  //       throw "Running in browser, but no storage adapter supported!";
  //     }
  //   } else {
  //     console.error("WARNING: falling back to in-memory storage");
  //     setAdapter(memoryAdapter());
  //   }
  // })();

  if(typeof(window) !== 'undefined') {
    setAdapter(localStorageAdapter(window.localStorage));
  } else {
    console.error("WARNING: falling back to in-memory storage");
    setAdapter(memoryAdapter());
  }

  //
  // Type: Node
  //
  // Represents a node within the local store.
  //
  // Properties:
  //   timestamp      - last time this node was (apparently) updated (default: 0)
  //   lastUpdatedAt  - Last time this node was upated from remote storage
  //   mimeType       - MIME media type
  //   diff           - (directories only) marks children that have been modified.
  //   data           - Actual data of the node. A String, a JSON-Object or an ArrayBuffer.
  //   binary         - boolean indicating if this node is binary. If true, 'data' is an ArrayBuffer.
  //

  // Event: change
  // See <BaseClient.Events>

  function fireChange(origin, path, oldValue) {
    return getNode(path).
      then(function(node) {
        events.emit('change', {
          path: path,
          origin: origin,
          oldValue: oldValue,
          newValue: node.data,
          timestamp: node.timestamp
        });
      });
  }

  // Event: foreign-change
  // Fired when a foreign node is updated.

  function fireForeignChange(path, oldValue) {
    return getNode(path).
      then(function(node) {
        events.emit('foreign-change', {
          path: path,
          oldValue: oldValue,
          newValue: node.data,
          timestamp: node.timestamp
        });
      });
  }

  //
  // Event: error
  // See <BaseClient.Events>

  //
  // Method: on
  //
  // Install an event handler
  // See <util.EventEmitter.on> for documentation.

  // Method: getNode
  // Get a node.
  //
  // Parameters:
  //   path - absolute path
  //
  // Returns:
  //   a node object. If no node is found at the given path, a new empty
  //   node object is constructed instead.
  function getNode(path) {
    logger.info('getNode', path);
    if(! path) {
      // FIXME: fail returned promise instead.
      throw new Error("No path given!");
    }
    validPath(path);
    return dataStore.get(path).then(function(node) {
      if(! node) {
        node = {//this is what an empty node looks like
          timestamp: 0,
          lastUpdatedAt: 0,
          mimeType: "application/json"
        };
        if(util.isDir(path)) {
          node.diff = {};
          node.data = {};
        }
      }

      return node;
    });
  }

  // Method: forgetAll
  // Forget all data stored by <store>.
  //
  function forgetAll() {
    return dataStore.forgetAll();
  }

  // Function: setNodeData
  //
  // update a node's payload
  //
  // Parameters:
  //   path      - absolute path from the storage root
  //   data      - node data to set, or undefined to delete the node
  //   outgoing  - boolean, whether this update is to be propagated
  //   timestamp - timestamp to set for the update
  //   mimeType  - MIME media type of the node's data
  //
  // Fires:
  //   change w/ origin=remote - unless this is an outgoing change
  //
  function setNodeData(path, data, outgoing, timestamp, mimeType) {
    return dataStore.transaction(true, function(transaction) {
      return getNode(path, transaction).then(function(node) {

        var oldValue = node.data;

        node.data = data;

        if(! outgoing) {
          if(typeof(timestamp) !== 'number') {
            throw "Attempted to set non-number timestamp in incoming change: " + timestamp + ' (' + typeof(timestamp) + ') at path ' + path;
          }
          node.lastUpdatedAt = timestamp;

          delete node.error;
        }

        if(! mimeType) {
          mimeType = 'application/json';
        }
        node.mimeType = mimeType;

        return updateNode(path, (typeof(node.data) !== 'undefined' ? node : undefined), outgoing, false, timestamp, oldValue, transaction).
          then(function() {
            transaction.commit();
          });
      });
    });
  }

  function setLastSynced(path, timestamp) {
    logger.info('setLastSynced', path, 'requested');
    return dataStore.transaction(true, function(transaction) {
      logger.info('setLastSynced', path, 'started');
      return getNode(path, transaction).then(function(node) {
        node.lastUpdatedAt = timestamp;
        return updateNode(path, node, false, true, undefined, undefined, transaction).
          then(function() {
            logger.info('setLastSynced', path, 'done', timestamp);
            transaction.commit();
          });
      });
    });
  }

  function removeNode(path, timestamp) {
    return setNodeData(path, undefined, false, timestamp || getCurrTimestamp());
  }

  function updateMetadata(path, attributes, node) {
    function doUpdate(node) {
      util.extend(node, attributes);
      return updateNode(path, node, false, true);
    }
    if(node) {
      return doUpdate(node);
    } else {
      return getNode(path).then(doUpdate);
    }
  }

  function setNodeError(path, error) {
    return updateMetadata(path, {
      error: error
    });
  }

  // Method: clearDiff
  //
  // Clear diff flag of given node on it's parent.
  //
  // Recurses upwards, when the parent's diff becomes empty.
  //
  // Clearing the diff is usually done, once the changes have been
  // propagated through sync.
  //
  // Parameters:
  //   path      - absolute path to the node
  //   timestamp - new timestamp (received from remote) to set on the node.
  //
  function clearDiff(path, timestamp) {
    return getNode(path).then(function(node) {

      function clearDiffOnParent() {
        var parentPath = util.containingDir(path);
        if(parentPath) {
          var baseName = util.baseName(path);
          return getNode(parentPath).then(function(parent) {
            delete parent.diff[baseName];
            if(Object.keys(parent.diff).length === 0) {
              parent.lastUpdatedAt = parent.timestamp;
            }
            return updateNode(parentPath, parent, false, true).then(function() {
              if(Object.keys(parent.diff).length === 0) {
                return clearDiff(parentPath, timestamp);
              }
            });
          });
        }
      }

      if(util.isDir(path) && Object.keys(node.data).length === 0) {
        // remove empty dir
        return updateNode(path, undefined, false, false).then(clearDiffOnParent);
      } else if(timestamp) {
        // set last updated
        node.timestamp = node.lastUpdatedAt = timestamp;
        return updateNode(path, node, false, true).then(clearDiffOnParent);
      } else {
        return clearDiffOnParent();
      }
    });
  }

  // Method: fireInitialEvents
  //
  // Fire a change event with origin=device for each node present in store.
  //
  // This is so apps don't need to add event handlers *and* initially request
  // listings to fill their views.
  //
  function fireInitialEvents() {
    logger.info('fire initial events');

    function iter(path) {
      if(util.isDir(path)) {
        return getNode(path).then(function(node) {
          if(node.data) {
            var keys = Object.keys(node.data);
            var next = function() {
              if(keys.length > 0) {
                return iter(path + keys.shift()).then(next);
              }
            };
            return next();
          }
        });
      } else {
        return fireChange('device', path);
      }
    }

    return iter('/');
  }

  function getFileName(path) {
    var parts = path.split('/');
    if(util.isDir(path)) {
      return parts[parts.length-2]+'/';
    } else {
      return parts[parts.length-1];
    }
  }

  function getCurrTimestamp() {
    return new Date().getTime();
  }

  function validPath(path) {
    if(! (path[0] == '/' || userAddressRE.test(path))) {
      throw new Error("Invalid path: " + path);
    }
  }

  function isForeign(path) {
    return path[0] != '/';
  }

  function determineDirTimestamp(path, transaction) {
    return getNode(path, transaction).
      then(function(node) {
        var t = 0;
        if(node.data) {
          for(var key in node.data) {
            if(node.data[key] > t) {
              t = node.data[key];
            }
          }
        }
        return t > 0 ? t : getCurrTimestamp();
      });
  }

  function touchNode(path) {
    return dataStore.transaction(true, function(transaction) {
      return getNode(path, transaction).then(function(node) {
        return updateNode(
          path, node, false, true, undefined, undefined, transaction
        ).then(transaction.commit);
      });
    });
  }

  // FIXME: this argument list is getting too long!!!
  function updateNode(path, node, outgoing, meta, timestamp, oldValue,
                      transaction) {
    logger.debug('updateNode', path, node, outgoing, meta, timestamp);

    validPath(path);

    function adjustTimestamp(transaction) {
      return util.getPromise(function(promise) {
        function setTimestamp(t) {
          if(t) { timestamp = t; }
          if(node && typeof(timestamp) == 'number') {
            node.timestamp = timestamp;
          }
          promise.fulfill();
        }
        if((!meta) && (! timestamp)) {
          if(outgoing) {
            timestamp = getCurrTimestamp();
            setTimestamp();
          } else if(util.isDir(path)) {
            determineDirTimestamp(path, transaction).then(setTimestamp);
          } else {
            throw new Error('no timestamp given for node ' + path);
          }
        } else {
          setTimestamp();
        }
      });
    }

    function storeNode(transaction) {
      if(node) {
        return transaction.set(path, node);
      } else {
        return transaction.remove(path);
      }
    }

    function updateParent(transaction) {
      var parentPath = util.containingDir(path);
      var baseName = util.baseName(path);
      if(parentPath) {
        return getNode(parentPath, transaction).
          then(function(parent) {
            if(meta) { // META
              if(! parent.data[baseName]) {
                parent.data[baseName] = 0;
                return updateNode(parentPath, parent, false, true, timestamp, undefined, transaction);
              }
            } else if(outgoing) { // OUTGOING
              if(node) {
                parent.data[baseName] = timestamp;
              } else {
                delete parent.data[baseName];
              }
              parent.diff[baseName] = timestamp;
              return updateNode(parentPath, parent, true, false, timestamp, undefined, transaction);
            } else { // INCOMING
              if(node) { // add or change
                if((! parent.data[baseName]) || parent.data[baseName] < timestamp) {
                  parent.data[baseName] = timestamp;
                  delete parent.diff[baseName];
                  return updateNode(parentPath, parent, false, false, timestamp, undefined, transaction);
                }
              } else { // deletion
                delete parent.data[baseName];
                delete parent.diff[baseName];
                return updateNode(parentPath, parent, false, false, timestamp, undefined, transaction);
              }
            }
          });
      }
    }

    function fireEvents() {
      if((!meta) && (! outgoing) && (! util.isDir(path))) {
        // fire changes
        if(isForeign(path)) {
          return fireForeignChange(path, oldValue);
        } else {
          return fireChange('remote', path, oldValue);
        }
      }
    }

    function doUpdate(transaction, dontCommit) {
      return adjustTimestamp(transaction).
        then(util.curry(storeNode, transaction)).
        then(util.curry(updateParent, transaction)).
        then(function() {
          if(! dontCommit) {
            transaction.commit();
          }
        }).
        then(fireEvents);
    }

    if(transaction) {
      return doUpdate(transaction, true);
    } else {
      return dataStore.transaction(true, doUpdate);
    }
  }

  return {

    memory: memoryAdapter,
    localStorage: localStorageAdapter,
    pending: pendingAdapter,

    events: events,

    // method         , local              , used by

    getNode           : getNode,          // sync
    setNodeData       : setNodeData,      // sync
    clearDiff         : clearDiff,        // sync
    removeNode        : removeNode,       // sync
    setLastSynced     : setLastSynced,    // sync
    touchNode         : touchNode,        // sync

    on                : events.on,
    emit              : events.emit,
    setNodeError      : setNodeError,

    forgetAll         : forgetAll,        // widget
    fireInitialEvents : fireInitialEvents,// widget

    setAdapter        : setAdapter,
    getAdapter        : function() { return dataStore; }
  };

  // Interface: StorageAdapter
  //
  // Backend for the <store>.
  //
  // Currently supported:
  // * memory
  // * localStorage
  // * indexedDB
  //
  // Planned:
  // * WebSQL
  //
  // Method: get(path)
  // Get node from given path
  // Returns a promise.
  //
  // Method: set(path, node)
  // Create / update node at given path. See <store.Node> for a reference on how nodes look.
  // Returns a promise.
  //
  // Method: remove(path)
  // Remove node from given path
  // Returns a promise.
  //
  // Method: forgetAll()
  // Remove all data.
  // Returns a promise.
  //
  // Method: on(eventName)
  // Install an event handler.
  //
  // Event: change
  // Fired when the store changes from another source (such as another tab / window).
  //

});
<commit_msg>initial events fired on page reload are simulated 'remote' events, not 'device' events.<commit_after>/*global window */
/*global console */

define([
  './util',
  './platform',
  './store/memory',
  './store/localStorage',
  './store/pending'
], function (util, platform, memoryAdapter, localStorageAdapter, pendingAdapter) {

  "use strict";

  // Namespace: store
  //
  // The store stores data locally. It treats all data as raw nodes, that have *metadata* and *payload*.
  // Where the actual data is stored is determined by the <StorageAdapter> that is being used.

  var logger = util.getLogger('store');

  // foreign nodes are prefixed with a user address
  var userAddressRE = /^[^@]+@[^:]+:\//;

  var events = util.getEventEmitter('error', 'change', 'foreign-change');

  var dataStore;

  // Method: setAdapter
  // Set the storage adapter. See <StorageAdapter> for a description of
  // the required interface.
  function setAdapter(adapter) {
    dataStore = adapter;
    // forward changes from data store (e.g. made in other tabs)
    dataStore.on('change', function(event) {
      if(! util.isDir(event.path)) {
        fireChange('device', event.path, event.oldValue);
      }
    });
  }

  // (function() {
  //   if(typeof(window) !== 'undefined') {
  //     var idb = indexedDbAdapter.detect();
  //     if(idb) {
  //       setAdapter(indexedDbAdapter(idb));
  //     } else if(typeof(window.openDatabase) !== 'undefined') {
  //       setAdapter(webSqlAdapter());
  //     } else if(typeof(window.localStorage) !== 'undefined') {
  //       setAdapter(localStorageAdapter(window.localStorage));
  //     } else {
  //       throw "Running in browser, but no storage adapter supported!";
  //     }
  //   } else {
  //     console.error("WARNING: falling back to in-memory storage");
  //     setAdapter(memoryAdapter());
  //   }
  // })();

  if(typeof(window) !== 'undefined') {
    setAdapter(localStorageAdapter(window.localStorage));
  } else {
    console.error("WARNING: falling back to in-memory storage");
    setAdapter(memoryAdapter());
  }

  //
  // Type: Node
  //
  // Represents a node within the local store.
  //
  // Properties:
  //   timestamp      - last time this node was (apparently) updated (default: 0)
  //   lastUpdatedAt  - Last time this node was upated from remote storage
  //   mimeType       - MIME media type
  //   diff           - (directories only) marks children that have been modified.
  //   data           - Actual data of the node. A String, a JSON-Object or an ArrayBuffer.
  //   binary         - boolean indicating if this node is binary. If true, 'data' is an ArrayBuffer.
  //

  // Event: change
  // See <BaseClient.Events>

  function fireChange(origin, path, oldValue) {
    return getNode(path).
      then(function(node) {
        events.emit('change', {
          path: path,
          origin: origin,
          oldValue: oldValue,
          newValue: node.data,
          timestamp: node.timestamp
        });
      });
  }

  // Event: foreign-change
  // Fired when a foreign node is updated.

  function fireForeignChange(path, oldValue) {
    return getNode(path).
      then(function(node) {
        events.emit('foreign-change', {
          path: path,
          oldValue: oldValue,
          newValue: node.data,
          timestamp: node.timestamp
        });
      });
  }

  //
  // Event: error
  // See <BaseClient.Events>

  //
  // Method: on
  //
  // Install an event handler
  // See <util.EventEmitter.on> for documentation.

  // Method: getNode
  // Get a node.
  //
  // Parameters:
  //   path - absolute path
  //
  // Returns:
  //   a node object. If no node is found at the given path, a new empty
  //   node object is constructed instead.
  function getNode(path) {
    logger.info('getNode', path);
    if(! path) {
      // FIXME: fail returned promise instead.
      throw new Error("No path given!");
    }
    validPath(path);
    return dataStore.get(path).then(function(node) {
      if(! node) {
        node = {//this is what an empty node looks like
          timestamp: 0,
          lastUpdatedAt: 0,
          mimeType: "application/json"
        };
        if(util.isDir(path)) {
          node.diff = {};
          node.data = {};
        }
      }

      return node;
    });
  }

  // Method: forgetAll
  // Forget all data stored by <store>.
  //
  function forgetAll() {
    return dataStore.forgetAll();
  }

  // Function: setNodeData
  //
  // update a node's payload
  //
  // Parameters:
  //   path      - absolute path from the storage root
  //   data      - node data to set, or undefined to delete the node
  //   outgoing  - boolean, whether this update is to be propagated
  //   timestamp - timestamp to set for the update
  //   mimeType  - MIME media type of the node's data
  //
  // Fires:
  //   change w/ origin=remote - unless this is an outgoing change
  //
  function setNodeData(path, data, outgoing, timestamp, mimeType) {
    return dataStore.transaction(true, function(transaction) {
      return getNode(path, transaction).then(function(node) {

        var oldValue = node.data;

        node.data = data;

        if(! outgoing) {
          if(typeof(timestamp) !== 'number') {
            throw "Attempted to set non-number timestamp in incoming change: " + timestamp + ' (' + typeof(timestamp) + ') at path ' + path;
          }
          node.lastUpdatedAt = timestamp;

          delete node.error;
        }

        if(! mimeType) {
          mimeType = 'application/json';
        }
        node.mimeType = mimeType;

        return updateNode(path, (typeof(node.data) !== 'undefined' ? node : undefined), outgoing, false, timestamp, oldValue, transaction).
          then(function() {
            transaction.commit();
          });
      });
    });
  }

  function setLastSynced(path, timestamp) {
    logger.info('setLastSynced', path, 'requested');
    return dataStore.transaction(true, function(transaction) {
      logger.info('setLastSynced', path, 'started');
      return getNode(path, transaction).then(function(node) {
        node.lastUpdatedAt = timestamp;
        return updateNode(path, node, false, true, undefined, undefined, transaction).
          then(function() {
            logger.info('setLastSynced', path, 'done', timestamp);
            transaction.commit();
          });
      });
    });
  }

  function removeNode(path, timestamp) {
    return setNodeData(path, undefined, false, timestamp || getCurrTimestamp());
  }

  function updateMetadata(path, attributes, node) {
    function doUpdate(node) {
      util.extend(node, attributes);
      return updateNode(path, node, false, true);
    }
    if(node) {
      return doUpdate(node);
    } else {
      return getNode(path).then(doUpdate);
    }
  }

  function setNodeError(path, error) {
    return updateMetadata(path, {
      error: error
    });
  }

  // Method: clearDiff
  //
  // Clear diff flag of given node on it's parent.
  //
  // Recurses upwards, when the parent's diff becomes empty.
  //
  // Clearing the diff is usually done, once the changes have been
  // propagated through sync.
  //
  // Parameters:
  //   path      - absolute path to the node
  //   timestamp - new timestamp (received from remote) to set on the node.
  //
  function clearDiff(path, timestamp) {
    return getNode(path).then(function(node) {

      function clearDiffOnParent() {
        var parentPath = util.containingDir(path);
        if(parentPath) {
          var baseName = util.baseName(path);
          return getNode(parentPath).then(function(parent) {
            delete parent.diff[baseName];
            if(Object.keys(parent.diff).length === 0) {
              parent.lastUpdatedAt = parent.timestamp;
            }
            return updateNode(parentPath, parent, false, true).then(function() {
              if(Object.keys(parent.diff).length === 0) {
                return clearDiff(parentPath, timestamp);
              }
            });
          });
        }
      }

      if(util.isDir(path) && Object.keys(node.data).length === 0) {
        // remove empty dir
        return updateNode(path, undefined, false, false).then(clearDiffOnParent);
      } else if(timestamp) {
        // set last updated
        node.timestamp = node.lastUpdatedAt = timestamp;
        return updateNode(path, node, false, true).then(clearDiffOnParent);
      } else {
        return clearDiffOnParent();
      }
    });
  }

  // Method: fireInitialEvents
  //
  // Fire a change event with origin=device for each node present in store.
  //
  // This is so apps don't need to add event handlers *and* initially request
  // listings to fill their views.
  //
  function fireInitialEvents() {
    logger.info('fire initial events');

    function iter(path) {
      if(util.isDir(path)) {
        return getNode(path).then(function(node) {
          if(node.data) {
            var keys = Object.keys(node.data);
            var next = function() {
              if(keys.length > 0) {
                return iter(path + keys.shift()).then(next);
              }
            };
            return next();
          }
        });
      } else {
        return fireChange('remote', path);
      }
    }

    return iter('/');
  }

  function getFileName(path) {
    var parts = path.split('/');
    if(util.isDir(path)) {
      return parts[parts.length-2]+'/';
    } else {
      return parts[parts.length-1];
    }
  }

  function getCurrTimestamp() {
    return new Date().getTime();
  }

  function validPath(path) {
    if(! (path[0] == '/' || userAddressRE.test(path))) {
      throw new Error("Invalid path: " + path);
    }
  }

  function isForeign(path) {
    return path[0] != '/';
  }

  function determineDirTimestamp(path, transaction) {
    return getNode(path, transaction).
      then(function(node) {
        var t = 0;
        if(node.data) {
          for(var key in node.data) {
            if(node.data[key] > t) {
              t = node.data[key];
            }
          }
        }
        return t > 0 ? t : getCurrTimestamp();
      });
  }

  function touchNode(path) {
    return dataStore.transaction(true, function(transaction) {
      return getNode(path, transaction).then(function(node) {
        return updateNode(
          path, node, false, true, undefined, undefined, transaction
        ).then(transaction.commit);
      });
    });
  }

  // FIXME: this argument list is getting too long!!!
  function updateNode(path, node, outgoing, meta, timestamp, oldValue,
                      transaction) {
    logger.debug('updateNode', path, node, outgoing, meta, timestamp);

    validPath(path);

    function adjustTimestamp(transaction) {
      return util.getPromise(function(promise) {
        function setTimestamp(t) {
          if(t) { timestamp = t; }
          if(node && typeof(timestamp) == 'number') {
            node.timestamp = timestamp;
          }
          promise.fulfill();
        }
        if((!meta) && (! timestamp)) {
          if(outgoing) {
            timestamp = getCurrTimestamp();
            setTimestamp();
          } else if(util.isDir(path)) {
            determineDirTimestamp(path, transaction).then(setTimestamp);
          } else {
            throw new Error('no timestamp given for node ' + path);
          }
        } else {
          setTimestamp();
        }
      });
    }

    function storeNode(transaction) {
      if(node) {
        return transaction.set(path, node);
      } else {
        return transaction.remove(path);
      }
    }

    function updateParent(transaction) {
      var parentPath = util.containingDir(path);
      var baseName = util.baseName(path);
      if(parentPath) {
        return getNode(parentPath, transaction).
          then(function(parent) {
            if(meta) { // META
              if(! parent.data[baseName]) {
                parent.data[baseName] = 0;
                return updateNode(parentPath, parent, false, true, timestamp, undefined, transaction);
              }
            } else if(outgoing) { // OUTGOING
              if(node) {
                parent.data[baseName] = timestamp;
              } else {
                delete parent.data[baseName];
              }
              parent.diff[baseName] = timestamp;
              return updateNode(parentPath, parent, true, false, timestamp, undefined, transaction);
            } else { // INCOMING
              if(node) { // add or change
                if((! parent.data[baseName]) || parent.data[baseName] < timestamp) {
                  parent.data[baseName] = timestamp;
                  delete parent.diff[baseName];
                  return updateNode(parentPath, parent, false, false, timestamp, undefined, transaction);
                }
              } else { // deletion
                delete parent.data[baseName];
                delete parent.diff[baseName];
                return updateNode(parentPath, parent, false, false, timestamp, undefined, transaction);
              }
            }
          });
      }
    }

    function fireEvents() {
      if((!meta) && (! outgoing) && (! util.isDir(path))) {
        // fire changes
        if(isForeign(path)) {
          return fireForeignChange(path, oldValue);
        } else {
          return fireChange('remote', path, oldValue);
        }
      }
    }

    function doUpdate(transaction, dontCommit) {
      return adjustTimestamp(transaction).
        then(util.curry(storeNode, transaction)).
        then(util.curry(updateParent, transaction)).
        then(function() {
          if(! dontCommit) {
            transaction.commit();
          }
        }).
        then(fireEvents);
    }

    if(transaction) {
      return doUpdate(transaction, true);
    } else {
      return dataStore.transaction(true, doUpdate);
    }
  }

  return {

    memory: memoryAdapter,
    localStorage: localStorageAdapter,
    pending: pendingAdapter,

    events: events,

    // method         , local              , used by

    getNode           : getNode,          // sync
    setNodeData       : setNodeData,      // sync
    clearDiff         : clearDiff,        // sync
    removeNode        : removeNode,       // sync
    setLastSynced     : setLastSynced,    // sync
    touchNode         : touchNode,        // sync

    on                : events.on,
    emit              : events.emit,
    setNodeError      : setNodeError,

    forgetAll         : forgetAll,        // widget
    fireInitialEvents : fireInitialEvents,// widget

    setAdapter        : setAdapter,
    getAdapter        : function() { return dataStore; }
  };

  // Interface: StorageAdapter
  //
  // Backend for the <store>.
  //
  // Currently supported:
  // * memory
  // * localStorage
  // * indexedDB
  //
  // Planned:
  // * WebSQL
  //
  // Method: get(path)
  // Get node from given path
  // Returns a promise.
  //
  // Method: set(path, node)
  // Create / update node at given path. See <store.Node> for a reference on how nodes look.
  // Returns a promise.
  //
  // Method: remove(path)
  // Remove node from given path
  // Returns a promise.
  //
  // Method: forgetAll()
  // Remove all data.
  // Returns a promise.
  //
  // Method: on(eventName)
  // Install an event handler.
  //
  // Event: change
  // Fired when the store changes from another source (such as another tab / window).
  //

});
<|endoftext|>


<commit_before>obtain(['./src/utils.js', 'child_process'], ({ copyConfigFile, call: Call }, { execSync })=> {

  var mainDir = __dirname.substring(0, __dirname.indexOf('piFig/src'));
  var startup = 'sudo startx ' + mainDir + 'node_modules/.bin/electron ' + mainDir;

  console.log(startup);

  exports.remove = ()=> {
    var command = 'sed -i".bak" "/node_modules\\/\\.bin\\/electron/d" /home/pi/.bashrc > /home/pi/.bashrc';
    console.log(command)
    if (__dirname.indexOf('/home/pi') >= 0) execSync(command);
    else console.error('System not a pi, preventing uninstall');
  };

  exports.configure = ()=> {
    exports.remove();
    var command = 'echo "' + startup + '" >> /home/pi/.bashrc';
    console.log(command);
    if (__dirname.indexOf('/home/pi') >= 0) execSync(command);
    else console.error('System not a pi, preventing install');
  };
});
<commit_msg>moved pifig to template<commit_after>obtain(['./src/utils.js', 'child_process'], ({ copyConfigFile, call: Call }, { execSync })=> {

  var mainDir = __dirname.substring(0, __dirname.indexOf('piFig/src'));
  var startup = 'sudo startx ' + mainDir + 'node_modules/.bin/electron ' + mainDir;

  console.log(startup);

  exports.remove = ()=> {
    var command = 'sed -i".bak" "/node_modules\\/\\.bin\\/electron/d" /home/pi/.bashrc';
    console.log(command);
    if (__dirname.indexOf('/home/pi') >= 0) execSync(command);
    else console.error('System not a pi, preventing uninstall');
  };

  exports.configure = ()=> {
    exports.remove();
    var command = 'echo "' + startup + '" >> /home/pi/.bashrc';
    console.log(command);
    if (__dirname.indexOf('/home/pi') >= 0) execSync(command);
    else console.error('System not a pi, preventing install');
  };
});
<|endoftext|>


<commit_before>import 'intersection-observer';
import 'requestidlecallback';
import objectFitImages  from 'object-fit-images';

objectFitImages();
<commit_msg>fix multiple space (#12655)<commit_after>import 'intersection-observer';
import 'requestidlecallback';
import objectFitImages from 'object-fit-images';

objectFitImages();
<|endoftext|>


<commit_before>function ChapterStatsController(chapterJSON) {
  var ChapterStatsCtrl = this;
  ChapterStatsCtrl.chapter = chapterJSON.data;

  // entries

  function filterWeeklyEntries(startOfWeek, entries) {
    var endOfWeek = new Date();
    endOfWeek.setDate(startOfWeek.getDate() + 7);
    var weeksEntries = []
    for (i = 0; i < entries.length; i++) {
      var entryDay = new Date(entries[i].day);
      if (entryDay > startOfWeek && entryDay <= endOfWeek) {
        weeksEntries.push(entries[i]);
      }
    }
    return weeksEntries;
  }

  var startOfThisWeek = new Date();
  startOfThisWeek.setDate(startOfThisWeek.getDate() - 7)
  var startOfLastWeek = new Date();
  startOfLastWeek.setDate(startOfLastWeek.getDate() - 14)
  var thisWeeksEntries = filterWeeklyEntries(startOfThisWeek, ChapterStatsCtrl.chapter.entries.slice(0, 7));
  var lastWeeksEntries = filterWeeklyEntries(startOfLastWeek, ChapterStatsCtrl.chapter.entries.slice(0, 14));

  // measurements

  function calculateWeeklyMeasurementAverages(measurements) {
    var weightVals = measurements.map(function(measurement) { return measurement.weight});
    var bodyfatVals = measurements.map(function(measurement) { return measurement.bodyfat; });
    var heightVals = measurements.map(function(measurement) { return measurement.height; });
    var waistVals = measurements.map(function(measurement) { return measurement.waist; });
    var chestVals = measurements.map(function(measurement) { return measurement.chest; });

    return {
      weight: findMean(weightVals),
      bodyfat: findMean(bodyfatVals),
      height: findMean(heightVals),
      waist: findMean(waistVals),
      chest: findMean(chestVals)
    };
  }

  function calculateMeasurementChange(firstMeasurements, secondMeasurements) {
    if (secondMeasurements) {
      return {
        weight:  (firstMeasurements.weight - secondMeasurements.weight),
        bodyfat: (firstMeasurements.bodyfat - secondMeasurements.bodyfat),
        height:  (firstMeasurements.height - secondMeasurements.height),
        waist: (firstMeasurements.waist - secondMeasurements.waist),
        chest:  (firstMeasurements.chest - secondMeasurements.chest)
      };
    }
    else {
      return {};
    }
  }

  ChapterStatsCtrl.firstEntryID = ChapterStatsCtrl.chapter.entries[ChapterStatsCtrl.chapter.entries.length - 1].id;
  ChapterStatsCtrl.firstMeasurement = ChapterStatsCtrl.chapter.measurements[ChapterStatsCtrl.chapter.measurements.length - 1] || {};
  var thisWeeksMeasurements = thisWeeksEntries.map(function(entry) { return entry.measurement; }).filter(notNull);
  var lastWeeksMeasurements = lastWeeksEntries.map(function(entry) { return entry.measurement; }).filter(notNull);

  ChapterStatsCtrl.thisWeekMeasurements = calculateWeeklyMeasurementAverages(thisWeeksMeasurements);
  ChapterStatsCtrl.lastWeekMeasurements = calculateWeeklyMeasurementAverages(lastWeeksMeasurements);

  ChapterStatsCtrl.measurementChange = calculateMeasurementChange(ChapterStatsCtrl.thisWeekMeasurements, ChapterStatsCtrl.lastWeekMeasurements);
  formatProperties(ChapterStatsCtrl.measurementChange, 1);

  // foods

  function calculateWeeklyIntakeAverages(foods, entryCount) {
    var calories = foods.map(function(food) { return food.calories });
    var protein = foods.map(function(food) { return food.protein});
    var carbs = foods.map(function(food) { return food.carbs });
    var fats = foods.map(function(food) { return food.fats });
    if (entryCount > 0) {
      return {
        calories: calories.reduce( (total, calories) => total + calories, 0)/entryCount,
        protein: protein.reduce( (total, protein) => total + protein, 0)/entryCount,
        carbs: carbs.reduce( (total, carbs) => total + carbs, 0)/entryCount,
        fats: fats.reduce( (total, fats) => total + fats, 0)/entryCount
      }
    } else {
      return {
        calories: undefined,
        protein: undefined,
        carbs: undefined,
        fats: undefined
      }
    }
  }

  function calculateIntakeChange(firstIntakes, secondIntakes) {
    return {
      calories: (firstIntakes.calories - secondIntakes.calories),
      protein: (firstIntakes.protein - secondIntakes.protein),
      carbs: (firstIntakes.carbs - secondIntakes.carbs),
      fats: (firstIntakes.fats - secondIntakes.fats)
    }
  }

  var thisWeeksFoods = thisWeeksEntries.map(function(entry) { return entry.foods }).reduce(function(allFoods, foods) { return allFoods.concat(foods) }, []);
  var lastWeeksFoods = lastWeeksEntries.map(function(entry) { return entry.foods }).reduce(function(allFoods, foods) { return allFoods.concat(foods) }, []);

  ChapterStatsCtrl.thisWeeksIntakes = calculateWeeklyIntakeAverages(thisWeeksFoods, thisWeeksEntries.length);
  ChapterStatsCtrl.lastWeeksIntakes = calculateWeeklyIntakeAverages(lastWeeksFoods, lastWeeksEntries.length);

  ChapterStatsCtrl.intakeChange = calculateIntakeChange(ChapterStatsCtrl.thisWeeksIntakes, ChapterStatsCtrl.lastWeeksIntakes);
  formatProperties(ChapterStatsCtrl.intakeChange, 0);

  // averages, helpers, formatters, etc.

  ChapterStatsCtrl.calculateAverage = function(total) {
    if (ChapterStatsCtrl.chapter.entries.length > 0) {
      return Math.round(total / ChapterStatsCtrl.chapter.entries.length)
    } else {
      return 0;
    }
  }

  function findMean(values) {
    var sum = 0;
    var count = 0;
    for (i = 0; i < values.length; i++) {
      if (values[i] !== null && values[i] !== undefined) {
        sum += values[i];
        count ++;
      }
    }
    if (count > 0) {
      return sum/count;
    } else {
      return undefined;
    }
  }

  function notNull(value) {
    return value !== null;
  }

  function formatProperties(object, fix) {
    for (var key in object) {
      if (object.hasOwnProperty(key)) {
        if (isNaN(object[key])) {
          object[key] = "N/A";
        } else {
          object[key] = object[key].toFixed(fix);
        }
      };
    }
  }

  if (ChapterStatsCtrl.chapter.completed_at !== null) {
    var daysPassed = Math.round((new Date(ChapterStatsCtrl.chapter.completed_at) - new Date(ChapterStatsCtrl.chapter.created_at)) / (60*60*24*1000));
  } else {
    var daysPassed = Math.round((new Date() - new Date(ChapterStatsCtrl.chapter.created_at)) / (60*60*24*1000));
  }
  if (daysPassed == 0) { daysPassed = 1; }

  ChapterStatsCtrl.averageChangeWeight = ((ChapterStatsCtrl.thisWeekMeasurements.weight - ChapterStatsCtrl.firstMeasurement.weight) / daysPassed).toFixed(2);
  if (ChapterStatsCtrl.averageChangeWeight === "NaN") { ChapterStatsCtrl.averageChangeWeight = "N/A" }

  ChapterStatsCtrl.estimatedTDE = ChapterStatsCtrl.averageChangeWeight * 500 + ChapterStatsCtrl.calculateAverage(ChapterStatsCtrl.chapter.total_calories) || "N/A";

}

angular
  .module('fitnessTracker')
    .controller('ChapterStatsController', ChapterStatsController);
<commit_msg>Update var startOfThisWeek and startOfLastWeek<commit_after>function ChapterStatsController(chapterJSON) {
  var ChapterStatsCtrl = this;
  ChapterStatsCtrl.chapter = chapterJSON.data;

  // entries

  function filterWeeklyEntries(startOfWeek, entries) {
    var endOfWeek = new Date();
    endOfWeek.setDate(startOfWeek.getDate() + 7);
    var weeksEntries = []
    for (i = 0; i < entries.length; i++) {
      var entryDay = new Date(entries[i].day);
      if (entryDay > startOfWeek && entryDay <= endOfWeek) {
        weeksEntries.push(entries[i]);
      }
    }
    return weeksEntries;
  }

  if (ChapterStatsCtrl.chapter.completed_at !== null) {
    var startOfThisWeek = new Date(ChapterStatsCtrl.chapter.completed_at);
    var startOfLastWeek = new Date(ChapterStatsCtrl.chapter.completed_at);
  } else {
    var startOfThisWeek = new Date();
    var startOfLastWeek = new Date();
  }

  startOfThisWeek.setDate(startOfThisWeek.getDate() - 7)
  startOfLastWeek.setDate(startOfLastWeek.getDate() - 14)

  var thisWeeksEntries = filterWeeklyEntries(startOfThisWeek, ChapterStatsCtrl.chapter.entries.slice(0, 7));
  var lastWeeksEntries = filterWeeklyEntries(startOfLastWeek, ChapterStatsCtrl.chapter.entries.slice(0, 14));

  // measurements

  function calculateWeeklyMeasurementAverages(measurements) {
    var weightVals = measurements.map(function(measurement) { return measurement.weight});
    var bodyfatVals = measurements.map(function(measurement) { return measurement.bodyfat; });
    var heightVals = measurements.map(function(measurement) { return measurement.height; });
    var waistVals = measurements.map(function(measurement) { return measurement.waist; });
    var chestVals = measurements.map(function(measurement) { return measurement.chest; });

    return {
      weight: findMean(weightVals),
      bodyfat: findMean(bodyfatVals),
      height: findMean(heightVals),
      waist: findMean(waistVals),
      chest: findMean(chestVals)
    };
  }

  function calculateMeasurementChange(firstMeasurements, secondMeasurements) {
    if (secondMeasurements) {
      return {
        weight:  (firstMeasurements.weight - secondMeasurements.weight),
        bodyfat: (firstMeasurements.bodyfat - secondMeasurements.bodyfat),
        height:  (firstMeasurements.height - secondMeasurements.height),
        waist: (firstMeasurements.waist - secondMeasurements.waist),
        chest:  (firstMeasurements.chest - secondMeasurements.chest)
      };
    }
    else {
      return {};
    }
  }

  ChapterStatsCtrl.firstEntryID = ChapterStatsCtrl.chapter.entries[ChapterStatsCtrl.chapter.entries.length - 1].id;
  ChapterStatsCtrl.firstMeasurement = ChapterStatsCtrl.chapter.measurements[ChapterStatsCtrl.chapter.measurements.length - 1] || {};
  var thisWeeksMeasurements = thisWeeksEntries.map(function(entry) { return entry.measurement; }).filter(notNull);
  var lastWeeksMeasurements = lastWeeksEntries.map(function(entry) { return entry.measurement; }).filter(notNull);

  ChapterStatsCtrl.thisWeekMeasurements = calculateWeeklyMeasurementAverages(thisWeeksMeasurements);
  ChapterStatsCtrl.lastWeekMeasurements = calculateWeeklyMeasurementAverages(lastWeeksMeasurements);

  ChapterStatsCtrl.measurementChange = calculateMeasurementChange(ChapterStatsCtrl.thisWeekMeasurements, ChapterStatsCtrl.lastWeekMeasurements);
  formatProperties(ChapterStatsCtrl.measurementChange, 1);

  // foods

  function calculateWeeklyIntakeAverages(foods, entryCount) {
    var calories = foods.map(function(food) { return food.calories });
    var protein = foods.map(function(food) { return food.protein});
    var carbs = foods.map(function(food) { return food.carbs });
    var fats = foods.map(function(food) { return food.fats });
    if (entryCount > 0) {
      return {
        calories: calories.reduce( (total, calories) => total + calories, 0)/entryCount,
        protein: protein.reduce( (total, protein) => total + protein, 0)/entryCount,
        carbs: carbs.reduce( (total, carbs) => total + carbs, 0)/entryCount,
        fats: fats.reduce( (total, fats) => total + fats, 0)/entryCount
      }
    } else {
      return {
        calories: undefined,
        protein: undefined,
        carbs: undefined,
        fats: undefined
      }
    }
  }

  function calculateIntakeChange(firstIntakes, secondIntakes) {
    return {
      calories: (firstIntakes.calories - secondIntakes.calories),
      protein: (firstIntakes.protein - secondIntakes.protein),
      carbs: (firstIntakes.carbs - secondIntakes.carbs),
      fats: (firstIntakes.fats - secondIntakes.fats)
    }
  }

  var thisWeeksFoods = thisWeeksEntries.map(function(entry) { return entry.foods }).reduce(function(allFoods, foods) { return allFoods.concat(foods) }, []);
  var lastWeeksFoods = lastWeeksEntries.map(function(entry) { return entry.foods }).reduce(function(allFoods, foods) { return allFoods.concat(foods) }, []);

  ChapterStatsCtrl.thisWeeksIntakes = calculateWeeklyIntakeAverages(thisWeeksFoods, thisWeeksEntries.length);
  ChapterStatsCtrl.lastWeeksIntakes = calculateWeeklyIntakeAverages(lastWeeksFoods, lastWeeksEntries.length);

  ChapterStatsCtrl.intakeChange = calculateIntakeChange(ChapterStatsCtrl.thisWeeksIntakes, ChapterStatsCtrl.lastWeeksIntakes);
  formatProperties(ChapterStatsCtrl.intakeChange, 0);

  // averages, helpers, formatters, etc.

  ChapterStatsCtrl.calculateAverage = function(total) {
    if (ChapterStatsCtrl.chapter.entries.length > 0) {
      return Math.round(total / ChapterStatsCtrl.chapter.entries.length)
    } else {
      return 0;
    }
  }

  function findMean(values) {
    var sum = 0;
    var count = 0;
    for (i = 0; i < values.length; i++) {
      if (values[i] !== null && values[i] !== undefined) {
        sum += values[i];
        count ++;
      }
    }
    if (count > 0) {
      return sum/count;
    } else {
      return undefined;
    }
  }

  function notNull(value) {
    return value !== null;
  }

  function formatProperties(object, fix) {
    for (var key in object) {
      if (object.hasOwnProperty(key)) {
        if (isNaN(object[key])) {
          object[key] = "N/A";
        } else {
          object[key] = object[key].toFixed(fix);
        }
      };
    }
  }

  if (ChapterStatsCtrl.chapter.completed_at !== null) {
    var daysPassed = Math.round((new Date(ChapterStatsCtrl.chapter.completed_at) - new Date(ChapterStatsCtrl.chapter.created_at)) / (60*60*24*1000));
  } else {
    var daysPassed = Math.round((new Date() - new Date(ChapterStatsCtrl.chapter.created_at)) / (60*60*24*1000));
  }
  if (daysPassed == 0) { daysPassed = 1; }

  ChapterStatsCtrl.averageChangeWeight = ((ChapterStatsCtrl.thisWeekMeasurements.weight - ChapterStatsCtrl.firstMeasurement.weight) / daysPassed).toFixed(2);
  if (ChapterStatsCtrl.averageChangeWeight === "NaN") { ChapterStatsCtrl.averageChangeWeight = "N/A" }

  ChapterStatsCtrl.estimatedTDE = ChapterStatsCtrl.averageChangeWeight * 500 + ChapterStatsCtrl.calculateAverage(ChapterStatsCtrl.chapter.total_calories) || "N/A";

}

angular
  .module('fitnessTracker')
    .controller('ChapterStatsController', ChapterStatsController);
<|endoftext|>