<commit_before>/**
 * Root component drawing whole widget.
 * @author Marián Hlaváč
 */

import React from 'react';
import Moment from 'moment';
import CP from 'counterpart';
import Hammer from 'hammerjs';

import Controls from './Controls.component';
import Timetable from './Timetable.component';
import DataCache from '../modules/DataCache.module';
import FunctionsSidebar from './FunctionsSidebar.component';
import Spinner from './Spinner.component';
import Error from './Error.component';

export default class Fittable extends React.Component
{
    constructor( props )
    {
        super.constructor( props );

        // Set initial states
        this.state = {
            viewDate: new Moment().startOf( 'isoweek' ),
            prevViewDate: new Moment().startOf( 'isoweek' ),
            layout: 'horizontal',
            selectedDay: new Moment().isoWeekday() - 1,
            displayFilter: {
                'laboratory': true,
                'tutorial': true,
                'lecture': true,
                'exam': true,
                'assessment': true,
                'onetime': true,
                'other': true
            },
            grid: {
                starts: 7.5,
                ends: 21.5,
                lessonDuration: 1.75
            },
            functionOpened: null,
            waiting: false,
            options: this.props,
            searchResults: [],
            error: false,
            errorType: null,
            mutedError: false
        };

        // Declare variables
        this.weekEvents = null;
        this.hammer = null;

        // Checks if required callbacks are set
        if ( typeof this.props.callbacks.data == 'undefined' )
            alert( 'You haven\'t set the data callback!' );

        // Force a refresh every one minute
        setInterval( this.handleRefreshNeed.bind( this ), 60000 );

        // Initial call of dateChange callback
        if ( 'dateChange' in this.props.callbacks )
        {
            this.props.callbacks.dateChange( this.state.viewDate.toISOString(), this.getSemester( this.state.viewDate ) );
        }
    }

    /**
     * Calls external data callback, telling that new data should be grabbed from source. If the required data are
     * present in cache, use them instead of calling data callback.
     */
    getWeekEvents( viewDate = null )
    {
        var newDate = viewDate == null ? this.state.viewDate : viewDate;

        var dateFrom = newDate.toISOString();
        var dateTo = new Moment( newDate ).endOf('isoweek').toISOString();

        // Try to load data from the cache
        var cacheData = DataCache.lookupCache( dateFrom, dateTo );

        if ( cacheData !== null ) {
            // Use cache data
            this.setWeekEvents( cacheData, true ).bind( this );
        }
        else {
            // Require new data
            this.props.callbacks.data( dateFrom, dateTo, this.setWeekEvents.bind( this ) );
        }
    }

    /**
     * Callback saving received data from source / cache to weekEvents variable.
     * If the data are from cache, the second argument should be true.
     * @param {Array} data Data array
     * @param {boolean} alreadyCached The data come from cache and shouldn't be cached again
     */
    setWeekEvents( data, alreadyCached = false )
    {
        // Animate in correct direction
        if ( 'timetable' in this.refs )
            if ( this.state.prevViewDate.isAfter( this.state.viewDate ) )
                this.refs.timetable.animateRight();
            else
                this.refs.timetable.animateLeft();

        // Cache data if needed
        if ( ! alreadyCached )
        {
            var dateFrom = this.state.viewDate.toISOString();
            var dateTo = new Moment( this.state.viewDate ).endOf( 'isoweek' ).toISOString();
            DataCache.cacheData( dateFrom, dateTo, data );
        }

        // Set the data into state
        if ( Fittable.areDataValid( data ) )
            this.setState( { weekEvents: data, waiting: false, prevViewDate: this.state.viewDate } );
        else
        {
            alert( 'Data invalid!' );
            this.setState( { waiting: false, prevViewDate: this.state.viewDate } );
        } // todo: alert through UI

    }

    /**
     * Tests if data are valid.
     * @param data Incoming data
     * @returns {boolean} Data are valid
     */
    static areDataValid( data )
    {
        if ( typeof data !== 'undefined' && data !== null )
        {
            for ( var event of data )
            {
                // Test if dates are valid ( using Moment.js validation )
                if ( ! new Moment( event.startsAt ).isValid() ) return false;
                if ( ! new Moment( event.endsAt ).isValid() ) return false;

                // todo: Tests missing!
            }

            // Everything seems correct
            return true;
        }
        else
            return false;
    }

    search( query )
    {
        this.setState( { waiting: true } );

        if ( 'search' in this.props.callbacks )
        {
            this.props.callbacks.search( query, this.receiveSearchResults.bind( this ) );
        }
        else
        {
            alert( 'Search callback hasn\'t been defined.' );
            // todo: do not alert, show UI error
        }
    }

    refresh()
    {
        this.getWeekEvents();
    }

    /**
     * Sets error flag and displays error message. The widget will not work anymore, needs reload or calling
     * the muteError method
     * @param errorType Type of error. Default is 'generic'
     */
    onError( errorType )
    {
        this.setState( {
            error: true,
            errorType: errorType
        } );
    }

    /**
     * Mutes displayed error
     */
    muteError()
    {
        this.setState( {
            error: false,
            mutedError: true
        } );
    }

    receiveSearchResults( data )
    {
        this.setState( { searchResults: data, waiting: false } );
    }

    /**
     * Gets actual semester name, determined from actual viewDate state.
     * @returns {string} Semester name
     */
    getSemester( viewDate )
    {
        var semestername;
        if ( viewDate.month() < 2 )
            semestername = CP.translate( 'winter_sem', { year: ( parseInt( viewDate.format( 'YYYY' ) ) - 1 ) + '/' + viewDate.format( 'YY' ) } );
        else if ( viewDate.month() < 10 )
            semestername = CP.translate( 'summer_sem', { year: ( parseInt( viewDate.format( 'YYYY' ) ) - 1 ) + '/' + viewDate.format( 'YY' ) } );
        else
            semestername = CP.translate( 'winter_sem', { year: ( viewDate.format( 'YYYY' ) ) + '/' + new Moment( viewDate ).add( 1, 'year' ).format( 'YY' ) } );

        return semestername;
    }

    /**
     * Changes view date to specified moment. The moment should be the beginning of the week.
     * @param {Moment} viewDate New view date
     */
    handleChangeViewDate( viewDate )
    {
        // Update viewDate
        var newdate = new Moment( viewDate );

        // Hide until the request isn't done
        this.refs.timetable.hide();

        // Update the viewDate state
        this.setState( { viewDate: newdate, waiting: true } );

        // Send new date through callback
        if ( 'dateChange' in this.props.callbacks )
        {
            this.props.callbacks.dateChange( newdate.toISOString(), this.getSemester( newdate ) );
        }

        // Update the data
        this.getWeekEvents( newdate );
    }

    /**
     * Change active selected day relatively by argument. Used for displaying single day on mobile devices.
     * @param {int} by relative change
     */
    handleChangeSelectedDay( by )
    {
        var selection = this.state.selectedDay + by;

        if ( selection > 6 )
        {
            selection -= 7;
            this.handleChangeViewDate( this.state.viewDate.add( 1, 'week' ) );
        }

        if ( selection < 0 )
        {
            selection += 7;
            this.handleChangeViewDate( this.state.viewDate.subtract( 1, 'week' ) );
        }

        this.setState( { selectedDay : selection } );
    }

    /**
     * Handler for settings changing event.
     * @param {string} key option key
     * @param {string} to new setting
     */
    handleChangeSetting( key, to )
    {
        var newOptions = this.state.options;
        newOptions[key] = to;

        this.setState( { options: newOptions } );
    }

    /**
     * Handler for filter changing event.
     * @param {Array} to New filter setting
     */
    handleChangeFilter( to )
    {
        this.setState( { displayFilter: to } );
    }

    /**
     * Opens different / closes settings panel in FunctionsSidebar component.
     * @param to Panel to change
     */
    handleChangeSettingsPanel( to )
    {
        this.setState( { functionOpened: ( this.state.functionOpened == to ? null : to ) } );
    }

    /**
     * Force a refresh by calling setState with no state update
     */
    handleRefreshNeed()
    {
        this.setState( {} );
    }

    handleChangeView( to, param )
    {
        if ( typeof this.props.callbacks.viewChange == 'undefined' )
            alert( 'You haven\'t set the view changing callback!' );

        this.props.callbacks.viewChange( to, param );
    }

    /**
     * Hangs a listener on whole fittable element, that listens to swipe touch events.
     * @param el Element to be listened on
     */
    registerSwipeListener( el )
    {
        this.hammer = new Hammer( el );

        this.hammer.on( 'swipe', function( e )
        {
            if ( window.innerWidth <= 768 ) this.handleChangeSelectedDay( e.velocityX > 0 ? 1 : -1 );
        }.bind( this ) );
    }

    /**
     * Cancel all hammer listeners
     */
    unregisterSwipeListener()
    {
        this.hammer.destroy();
    }

    componentWillMount()
    {
        // Get week events
        this.getWeekEvents();
    }

    /**
     * Component mounting
     */
    componentDidMount()
    {
        this.registerSwipeListener( this.refs.rootEl.getDOMNode() );
    }

    /**
     * Component unmounting
     */
    componentWillUnmount()
    {
        this.unregisterSwipeListener();
    }

    /**
     * Renders the component
     */
    render()
    {
        if ( ! this.state.error )
        {
            return

                <Error muted={true} shown={this.state.mutedError} type={this.state.errorType} />

                <Controls viewDate={this.state.viewDate} onWeekChange={this.handleChangeViewDate.bind( this )}
                    onDateChange={this.handleChangeViewDate.bind( this )} semester={this.getSemester( this.state.viewDate )}
                    onSettingsPanelChange={this.handleChangeSettingsPanel.bind( this )}
                    onSelDayChange={this.handleChangeSelectedDay.bind( this )} selectedDay={this.state.selectedDay} />

                <FunctionsSidebar ref="sidebar" opened={this.state.functionOpened} displayFilter={this.state.displayFilter}
                    onFilterChange={this.handleChangeFilter.bind( this )} onSettingChange={this.handleChangeSetting.bind( this )}
                    onRefreshNeed={this.handleRefreshNeed.bind( this )} options={this.state.options}
                    onViewChange={this.handleChangeView.bind( this )}
                    onSearch={this.search.bind( this )} searchResults={this.state.searchResults} />

                <Timetable grid={this.state.grid} viewDate={this.state.viewDate} layout={this.state.options.layout}
                    weekEvents={this.state.weekEvents} displayFilter={this.state.displayFilter}
                    functionsOpened={this.state.functionOpened} selectedDay={this.state.selectedDay}
                    onViewChange={this.handleChangeView.bind( this )}
                    colored={this.state.options.colors} days7={this.state.options.days7} ref="timetable" />

                <Spinner show={this.state.waiting} />

;
        }
        else
        {
            return

                <Error type={this.state.errorType} onMute={this.muteError.bind(this)} />

;
        }
    }
}

Fittable.defaultProps = { callbacks: null, locale: 'en', layout: 'horizontal', colors: false, days7: false };
<commit_msg>Close all functions when changing the view Resolves #12<commit_after>/**
 * Root component drawing whole widget.
 * @author Marián Hlaváč
 */

import React from 'react';
import Moment from 'moment';
import CP from 'counterpart';
import Hammer from 'hammerjs';

import Controls from './Controls.component';
import Timetable from './Timetable.component';
import DataCache from '../modules/DataCache.module';
import FunctionsSidebar from './FunctionsSidebar.component';
import Spinner from './Spinner.component';
import Error from './Error.component';

export default class Fittable extends React.Component
{
    constructor( props )
    {
        super.constructor( props );

        // Set initial states
        this.state = {
            viewDate: new Moment().startOf( 'isoweek' ),
            prevViewDate: new Moment().startOf( 'isoweek' ),
            layout: 'horizontal',
            selectedDay: new Moment().isoWeekday() - 1,
            displayFilter: {
                'laboratory': true,
                'tutorial': true,
                'lecture': true,
                'exam': true,
                'assessment': true,
                'onetime': true,
                'other': true
            },
            grid: {
                starts: 7.5,
                ends: 21.5,
                lessonDuration: 1.75
            },
            functionOpened: null,
            waiting: false,
            options: this.props,
            searchResults: [],
            error: false,
            errorType: null,
            mutedError: false
        };

        // Declare variables
        this.weekEvents = null;
        this.hammer = null;

        // Checks if required callbacks are set
        if ( typeof this.props.callbacks.data == 'undefined' )
            alert( 'You haven\'t set the data callback!' );

        // Force a refresh every one minute
        setInterval( this.handleRefreshNeed.bind( this ), 60000 );

        // Initial call of dateChange callback
        if ( 'dateChange' in this.props.callbacks )
        {
            this.props.callbacks.dateChange( this.state.viewDate.toISOString(), this.getSemester( this.state.viewDate ) );
        }
    }

    /**
     * Calls external data callback, telling that new data should be grabbed from source. If the required data are
     * present in cache, use them instead of calling data callback.
     */
    getWeekEvents( viewDate = null )
    {
        var newDate = viewDate == null ? this.state.viewDate : viewDate;

        var dateFrom = newDate.toISOString();
        var dateTo = new Moment( newDate ).endOf('isoweek').toISOString();

        // Try to load data from the cache
        var cacheData = DataCache.lookupCache( dateFrom, dateTo );

        if ( cacheData !== null ) {
            // Use cache data
            this.setWeekEvents( cacheData, true ).bind( this );
        }
        else {
            // Require new data
            this.props.callbacks.data( dateFrom, dateTo, this.setWeekEvents.bind( this ) );
        }
    }

    /**
     * Callback saving received data from source / cache to weekEvents variable.
     * If the data are from cache, the second argument should be true.
     * @param {Array} data Data array
     * @param {boolean} alreadyCached The data come from cache and shouldn't be cached again
     */
    setWeekEvents( data, alreadyCached = false )
    {
        // Animate in correct direction
        if ( 'timetable' in this.refs )
            if ( this.state.prevViewDate.isAfter( this.state.viewDate ) )
                this.refs.timetable.animateRight();
            else
                this.refs.timetable.animateLeft();

        // Cache data if needed
        if ( ! alreadyCached )
        {
            var dateFrom = this.state.viewDate.toISOString();
            var dateTo = new Moment( this.state.viewDate ).endOf( 'isoweek' ).toISOString();
            DataCache.cacheData( dateFrom, dateTo, data );
        }

        // Set the data into state
        if ( Fittable.areDataValid( data ) )
            this.setState( { weekEvents: data, waiting: false, prevViewDate: this.state.viewDate } );
        else
        {
            alert( 'Data invalid!' );
            this.setState( { waiting: false, prevViewDate: this.state.viewDate } );
        } // todo: alert through UI

    }

    /**
     * Tests if data are valid.
     * @param data Incoming data
     * @returns {boolean} Data are valid
     */
    static areDataValid( data )
    {
        if ( typeof data !== 'undefined' && data !== null )
        {
            for ( var event of data )
            {
                // Test if dates are valid ( using Moment.js validation )
                if ( ! new Moment( event.startsAt ).isValid() ) return false;
                if ( ! new Moment( event.endsAt ).isValid() ) return false;

                // todo: Tests missing!
            }

            // Everything seems correct
            return true;
        }
        else
            return false;
    }

    search( query )
    {
        this.setState( { waiting: true } );

        if ( 'search' in this.props.callbacks )
        {
            this.props.callbacks.search( query, this.receiveSearchResults.bind( this ) );
        }
        else
        {
            alert( 'Search callback hasn\'t been defined.' );
            // todo: do not alert, show UI error
        }
    }

    refresh()
    {
        this.getWeekEvents();
    }

    /**
     * Sets error flag and displays error message. The widget will not work anymore, needs reload or calling
     * the muteError method
     * @param errorType Type of error. Default is 'generic'
     */
    onError( errorType )
    {
        this.setState( {
            error: true,
            errorType: errorType
        } );
    }

    /**
     * Mutes displayed error
     */
    muteError()
    {
        this.setState( {
            error: false,
            mutedError: true
        } );
    }

    receiveSearchResults( data )
    {
        this.setState( { searchResults: data, waiting: false } );
    }

    /**
     * Gets actual semester name, determined from actual viewDate state.
     * @returns {string} Semester name
     */
    getSemester( viewDate )
    {
        var semestername;
        if ( viewDate.month() < 2 )
            semestername = CP.translate( 'winter_sem', { year: ( parseInt( viewDate.format( 'YYYY' ) ) - 1 ) + '/' + viewDate.format( 'YY' ) } );
        else if ( viewDate.month() < 10 )
            semestername = CP.translate( 'summer_sem', { year: ( parseInt( viewDate.format( 'YYYY' ) ) - 1 ) + '/' + viewDate.format( 'YY' ) } );
        else
            semestername = CP.translate( 'winter_sem', { year: ( viewDate.format( 'YYYY' ) ) + '/' + new Moment( viewDate ).add( 1, 'year' ).format( 'YY' ) } );

        return semestername;
    }

    /**
     * Changes view date to specified moment. The moment should be the beginning of the week.
     * @param {Moment} viewDate New view date
     */
    handleChangeViewDate( viewDate )
    {
        // Update viewDate
        var newdate = new Moment( viewDate );

        // Hide until the request isn't done
        this.refs.timetable.hide();

        // Update the viewDate state
        this.setState( { viewDate: newdate, waiting: true } );

        // Send new date through callback
        if ( 'dateChange' in this.props.callbacks )
        {
            this.props.callbacks.dateChange( newdate.toISOString(), this.getSemester( newdate ) );
        }

        // Update the data
        this.getWeekEvents( newdate );
    }

    /**
     * Change active selected day relatively by argument. Used for displaying single day on mobile devices.
     * @param {int} by relative change
     */
    handleChangeSelectedDay( by )
    {
        var selection = this.state.selectedDay + by;

        if ( selection > 6 )
        {
            selection -= 7;
            this.handleChangeViewDate( this.state.viewDate.add( 1, 'week' ) );
        }

        if ( selection < 0 )
        {
            selection += 7;
            this.handleChangeViewDate( this.state.viewDate.subtract( 1, 'week' ) );
        }

        this.setState( { selectedDay : selection } );
    }

    /**
     * Handler for settings changing event.
     * @param {string} key option key
     * @param {string} to new setting
     */
    handleChangeSetting( key, to )
    {
        var newOptions = this.state.options;
        newOptions[key] = to;

        this.setState( { options: newOptions } );
    }

    /**
     * Handler for filter changing event.
     * @param {Array} to New filter setting
     */
    handleChangeFilter( to )
    {
        this.setState( { displayFilter: to } );
    }

    /**
     * Opens different / closes settings panel in FunctionsSidebar component.
     * @param to Panel to change
     */
    handleChangeSettingsPanel( to )
    {
        this.setState( { functionOpened: ( this.state.functionOpened == to ? null : to ) } );
    }

    /**
     * Force a refresh by calling setState with no state update
     */
    handleRefreshNeed()
    {
        this.setState( {} );
    }

    handleChangeView( to, param )
    {
        if ( typeof this.props.callbacks.viewChange == 'undefined' )
            alert( 'You haven\'t set the view changing callback!' );

        // Close all opened functions
        this.setState( { functionOpened: null } );

        this.props.callbacks.viewChange( to, param );
    }

    /**
     * Hangs a listener on whole fittable element, that listens to swipe touch events.
     * @param el Element to be listened on
     */
    registerSwipeListener( el )
    {
        this.hammer = new Hammer( el );

        this.hammer.on( 'swipe', function( e )
        {
            if ( window.innerWidth <= 768 ) this.handleChangeSelectedDay( e.velocityX > 0 ? 1 : -1 );
        }.bind( this ) );
    }

    /**
     * Cancel all hammer listeners
     */
    unregisterSwipeListener()
    {
        this.hammer.destroy();
    }

    componentWillMount()
    {
        // Get week events
        this.getWeekEvents();
    }

    /**
     * Component mounting
     */
    componentDidMount()
    {
        this.registerSwipeListener( this.refs.rootEl.getDOMNode() );
    }

    /**
     * Component unmounting
     */
    componentWillUnmount()
    {
        this.unregisterSwipeListener();
    }

    /**
     * Renders the component
     */
    render()
    {
        if ( ! this.state.error )
        {
            return

                <Error muted={true} shown={this.state.mutedError} type={this.state.errorType} />

                <Controls viewDate={this.state.viewDate} onWeekChange={this.handleChangeViewDate.bind( this )}
                    onDateChange={this.handleChangeViewDate.bind( this )} semester={this.getSemester( this.state.viewDate )}
                    onSettingsPanelChange={this.handleChangeSettingsPanel.bind( this )}
                    onSelDayChange={this.handleChangeSelectedDay.bind( this )} selectedDay={this.state.selectedDay} />

                <FunctionsSidebar ref="sidebar" opened={this.state.functionOpened} displayFilter={this.state.displayFilter}
                    onFilterChange={this.handleChangeFilter.bind( this )} onSettingChange={this.handleChangeSetting.bind( this )}
                    onRefreshNeed={this.handleRefreshNeed.bind( this )} options={this.state.options}
                    onViewChange={this.handleChangeView.bind( this )}
                    onSearch={this.search.bind( this )} searchResults={this.state.searchResults} />

                <Timetable grid={this.state.grid} viewDate={this.state.viewDate} layout={this.state.options.layout}
                    weekEvents={this.state.weekEvents} displayFilter={this.state.displayFilter}
                    functionsOpened={this.state.functionOpened} selectedDay={this.state.selectedDay}
                    onViewChange={this.handleChangeView.bind( this )}
                    colored={this.state.options.colors} days7={this.state.options.days7} ref="timetable" />

                <Spinner show={this.state.waiting} />

;
        }
        else
        {
            return

                <Error type={this.state.errorType} onMute={this.muteError.bind(this)} />

;
        }
    }
}

Fittable.defaultProps = { callbacks: null, locale: 'en', layout: 'horizontal', colors: false, days7: false };
<|endoftext|>


<commit_before>'use strict';

angular.module('copayApp.controllers').controller('txpDetailsController', function($scope, $rootScope, $timeout, $interval, $log, ongoingProcess, platformInfo, $ionicScrollDelegate, txFormatService, bwcError, gettextCatalog, lodash, walletService, popupService, $ionicHistory) {
  var isGlidera = $scope.isGlidera;
  var GLIDERA_LOCK_TIME = 6 * 60 * 60;
  var now = Math.floor(Date.now() / 1000);
  var countDown;
  var listeners = [];

  $scope.init = function() {
    $scope.loading = null;
    $scope.isCordova = platformInfo.isCordova;
    $scope.copayerId = $scope.wallet.credentials.copayerId;
    $scope.isShared = $scope.wallet.credentials.n > 1;
    $scope.canSign = $scope.wallet.canSign() || $scope.wallet.isPrivKeyExternal();
    $scope.color = $scope.wallet.color;
    $scope.data = {};
    $scope.hasClick = platformInfo.hasClick;
    $scope.displayAmount = getDisplayAmount($scope.tx.amountStr);
    $scope.displayUnit = getDisplayUnit($scope.tx.amountStr);
    initActionList();
    checkPaypro();
  }

  function getDisplayAmount(amountStr) {
    return amountStr.split(' ')[0];
  }

  function getDisplayUnit(amountStr) {
    return amountStr.split(' ')[1];
  }

  function initActionList() {
    $scope.actionList = [];

    if (!$scope.isShared) return;

    var actionDescriptions = {
      created: gettextCatalog.getString('Proposal Created'),
      accept: gettextCatalog.getString('Accepted'),
      reject: gettextCatalog.getString('Rejected'),
      broadcasted: gettextCatalog.getString('Broadcasted'),
    };

    $scope.actionList.push({
      type: 'created',
      time: $scope.tx.createdOn,
      description: actionDescriptions['created'],
      by: $scope.tx.creatorName
    });

    lodash.each($scope.tx.actions, function(action) {
      $scope.actionList.push({
        type: action.type,
        time: action.createdOn,
        description: actionDescriptions[action.type],
        by: action.copayerName
      });
    });
  };

  function checkPaypro() {
    if ($scope.tx.payProUrl && !platformInfo.isChromeApp) {
      $scope.wallet.fetchPayPro({
        payProUrl: $scope.tx.payProUrl,
      }, function(err, paypro) {
        if (err) return;
        $scope.tx.paypro = paypro;
        paymentTimeControl($scope.tx.paypro.expires);
        $timeout(function() {
          $ionicScrollDelegate.resize();
        }, 10);
      });
    }
  };

  function paymentTimeControl(expirationTime) {
    $scope.paymentExpired = false;
    setExpirationTime();

    countDown = $interval(function() {
      setExpirationTime();
    }, 1000);

    function setExpirationTime() {
      var now = Math.floor(Date.now() / 1000);
      if (now > expirationTime) {
        $scope.paymentExpired = true;
        if (countDown) $interval.cancel(countDown);
        return;
      }
      var totalSecs = expirationTime - now;
      var m = Math.floor(totalSecs / 60);
      var s = totalSecs % 60;
      $scope.expires = ('0' + m).slice(-2) + ":" + ('0' + s).slice(-2);
    };
  };

  $scope.$on('accepted', function(event) {
    $scope.sign();
  });

  // ToDo: use tx.customData instead of tx.message
  if ($scope.tx.message === 'Glidera transaction' && isGlidera) {
    $scope.tx.isGlidera = true;
    if ($scope.tx.canBeRemoved) {
      $scope.tx.canBeRemoved = (Date.now() / 1000 - ($scope.tx.ts || $scope.tx.createdOn)) > GLIDERA_LOCK_TIME;
    }
  }

  var setError = function (err, prefix) {
    $scope.sendStatus = '';
    $scope.loading = false;
    popupService.showAlert(gettextCatalog.getString('Error'), bwcError.msg(err, prefix));
  };

  $scope.sign = function(onSendStatusChange) {
    $scope.loading = true;
    walletService.publishAndSign($scope.wallet, $scope.tx, function(err, txp) {
      $scope.$emit('UpdateTx');
      if (err) return setError(err, gettextCatalog.getString('Could not send payment'));
      success();
    }, onSendStatusChange);
  };

  $scope.reject = function(txp) {
    var title = gettextCatalog.getString('Warning!');
    var msg = gettextCatalog.getString('Are you sure you want to reject this transaction?');
    popupService.showConfirm(title, msg, null, null, function(res) {
      if (res) {
        $scope.loading = true;

        walletService.reject($scope.wallet, $scope.tx, function(err, txpr) {
          if (err)
            return setError(err, gettextCatalog.getString('Could not reject payment'));

          $scope.close();
        });
      }
    });
  };

  $scope.remove = function() {
    $scope.loading = true;

    $timeout(function() {
      ongoingProcess.set('removeTx', true);
      walletService.removeTx($scope.wallet, $scope.tx, function(err) {
        ongoingProcess.set('removeTx', false);

        // Hacky: request tries to parse an empty response
        if (err && !(err.message && err.message.match(/Unexpected/))) {
          $scope.$emit('UpdateTx');
          return setError(err, gettextCatalog.getString('Could not delete payment proposal'));
        }

        $scope.close();
      });
    }, 10);
  };

  $scope.broadcast = function(txp) {
    $scope.loading = true;

    $timeout(function() {
      ongoingProcess.set('broadcastTx', true);
      walletService.broadcastTx($scope.wallet, $scope.tx, function(err, txpb) {
        ongoingProcess.set('broadcastTx', false);

        if (err) {
          return setError(err, gettextCatalog.getString('Could not broadcast payment'));
        }

        $scope.close();
      });
    }, 10);
  };

  $scope.getShortNetworkName = function() {
    return $scope.wallet.credentials.networkName.substring(0, 4);
  };

  var updateTxInfo = function(eventName) {
    $scope.wallet.getTx($scope.tx.id, function(err, tx) {
      if (err) {
        if (err.message && err.message == 'TX_NOT_FOUND' &&
            (eventName == 'transactionProposalRemoved' || eventName == 'TxProposalRemoved')) {
          $scope.tx.removed = true;
          $scope.tx.canBeRemoved = false;
          $scope.tx.pendingForUs = false;
          $scope.$apply();
        }
        return;
      }

      var action = lodash.find(tx.actions, {
        copayerId: $scope.wallet.credentials.copayerId
      });

      $scope.tx = txFormatService.processTx(tx);

      if (!action && tx.status == 'pending')
        $scope.tx.pendingForUs = true;

      $scope.updateCopayerList();
      initActionList();
      $scope.$apply();
    });
  };

  var bwsEvent =  $rootScope.$on('bwsEvent', function(e, walletId, type, n) {
    lodash.each([
        'TxProposalRejectedBy',
        'TxProposalAcceptedBy',
        'transactionProposalRemoved',
        'TxProposalRemoved',
        'NewOutgoingTx',
        'UpdateTx'
    ], function(eventName) {
      if (walletId == $scope.wallet.id && type == eventName) {
        updateTxInfo(eventName);
      }
    });
  });

  $scope.updateCopayerList = function() {
    lodash.map($scope.copayers, function(cp) {
      lodash.each($scope.tx.actions, function(ac) {
        if (cp.id == ac.copayerId) {
          cp.action = ac.type;
        }
      });
    });
  };

  function statusChangeHandler(processName, showName, isOn) {
    $log.debug('statusChangeHandler: ', processName, showName, isOn);
    if (showName) {
      $scope.sendStatus = showName;
    }
  }

  function success() {
    $scope.sendStatus = 'success';
    $scope.$digest();
  }

  $scope.statusChangeHandler = statusChangeHandler;

  $scope.onConfirm = function() {
    $scope.sign(statusChangeHandler);
  };

  $scope.onSuccessConfirm = function() {
    $ionicHistory.nextViewOptions({
      disableAnimate: true
    });
    $scope.close();
  };

  $scope.close = function() {
    bwsEvent();
    $scope.loading = null;
    $scope.txpDetailsModal.hide();
  };
});
<commit_msg>Adds confirmation before delete transaction. Fix delete transaction<commit_after>'use strict';

angular.module('copayApp.controllers').controller('txpDetailsController', function($scope, $rootScope, $timeout, $interval, $log, ongoingProcess, platformInfo, $ionicScrollDelegate, txFormatService, bwcError, gettextCatalog, lodash, walletService, popupService, $ionicHistory) {
  var isGlidera = $scope.isGlidera;
  var GLIDERA_LOCK_TIME = 6 * 60 * 60;
  var now = Math.floor(Date.now() / 1000);
  var countDown;
  var listeners = [];

  $scope.init = function() {
    $scope.loading = null;
    $scope.isCordova = platformInfo.isCordova;
    $scope.copayerId = $scope.wallet.credentials.copayerId;
    $scope.isShared = $scope.wallet.credentials.n > 1;
    $scope.canSign = $scope.wallet.canSign() || $scope.wallet.isPrivKeyExternal();
    $scope.color = $scope.wallet.color;
    $scope.data = {};
    $scope.hasClick = platformInfo.hasClick;
    $scope.displayAmount = getDisplayAmount($scope.tx.amountStr);
    $scope.displayUnit = getDisplayUnit($scope.tx.amountStr);
    initActionList();
    checkPaypro();
  }

  function getDisplayAmount(amountStr) {
    return amountStr.split(' ')[0];
  }

  function getDisplayUnit(amountStr) {
    return amountStr.split(' ')[1];
  }

  function initActionList() {
    $scope.actionList = [];

    if (!$scope.isShared) return;

    var actionDescriptions = {
      created: gettextCatalog.getString('Proposal Created'),
      accept: gettextCatalog.getString('Accepted'),
      reject: gettextCatalog.getString('Rejected'),
      broadcasted: gettextCatalog.getString('Broadcasted'),
    };

    $scope.actionList.push({
      type: 'created',
      time: $scope.tx.createdOn,
      description: actionDescriptions['created'],
      by: $scope.tx.creatorName
    });

    lodash.each($scope.tx.actions, function(action) {
      $scope.actionList.push({
        type: action.type,
        time: action.createdOn,
        description: actionDescriptions[action.type],
        by: action.copayerName
      });
    });
  };

  function checkPaypro() {
    if ($scope.tx.payProUrl && !platformInfo.isChromeApp) {
      $scope.wallet.fetchPayPro({
        payProUrl: $scope.tx.payProUrl,
      }, function(err, paypro) {
        if (err) return;
        $scope.tx.paypro = paypro;
        paymentTimeControl($scope.tx.paypro.expires);
        $timeout(function() {
          $ionicScrollDelegate.resize();
        }, 10);
      });
    }
  };

  function paymentTimeControl(expirationTime) {
    $scope.paymentExpired = false;
    setExpirationTime();

    countDown = $interval(function() {
      setExpirationTime();
    }, 1000);

    function setExpirationTime() {
      var now = Math.floor(Date.now() / 1000);
      if (now > expirationTime) {
        $scope.paymentExpired = true;
        if (countDown) $interval.cancel(countDown);
        return;
      }
      var totalSecs = expirationTime - now;
      var m = Math.floor(totalSecs / 60);
      var s = totalSecs % 60;
      $scope.expires = ('0' + m).slice(-2) + ":" + ('0' + s).slice(-2);
    };
  };

  $scope.$on('accepted', function(event) {
    $scope.sign();
  });

  // ToDo: use tx.customData instead of tx.message
  if ($scope.tx.message === 'Glidera transaction' && isGlidera) {
    $scope.tx.isGlidera = true;
    if ($scope.tx.canBeRemoved) {
      $scope.tx.canBeRemoved = (Date.now() / 1000 - ($scope.tx.ts || $scope.tx.createdOn)) > GLIDERA_LOCK_TIME;
    }
  }

  var setError = function (err, prefix) {
    $scope.sendStatus = '';
    $scope.loading = false;
    popupService.showAlert(gettextCatalog.getString('Error'), bwcError.msg(err, prefix));
  };

  $scope.sign = function(onSendStatusChange) {
    $scope.loading = true;
    walletService.publishAndSign($scope.wallet, $scope.tx, function(err, txp) {
      $scope.$emit('UpdateTx');
      if (err) return setError(err, gettextCatalog.getString('Could not send payment'));
      success();
    }, onSendStatusChange);
  };

  $scope.reject = function(txp) {
    var title = gettextCatalog.getString('Warning!');
    var msg = gettextCatalog.getString('Are you sure you want to reject this transaction?');
    popupService.showConfirm(title, msg, null, null, function(res) {
      if (res) {
        $scope.loading = true;

        walletService.reject($scope.wallet, $scope.tx, function(err, txpr) {
          if (err)
            return setError(err, gettextCatalog.getString('Could not reject payment'));

          $scope.close();
        });
      }
    });
  };

  $scope.remove = function() {
    var title = gettextCatalog.getString('Warning!');
    var msg = gettextCatalog.getString('Are you sure you want to remove this transaction?');
    popupService.showConfirm(title, msg, null, null, function(res) {
      if (res) {
        ongoingProcess.set('removeTx', true);
        walletService.removeTx($scope.wallet, $scope.tx, function(err) {
          ongoingProcess.set('removeTx', false);

          // Hacky: request tries to parse an empty response
          if (err && !(err.message && err.message.match(/Unexpected/))) {
            $scope.$emit('UpdateTx');
            return setError(err, gettextCatalog.getString('Could not delete payment proposal'));
          }

          $scope.close();
        });
      }
    });
  };

  $scope.broadcast = function(txp) {
    $scope.loading = true;

    $timeout(function() {
      ongoingProcess.set('broadcastTx', true);
      walletService.broadcastTx($scope.wallet, $scope.tx, function(err, txpb) {
        ongoingProcess.set('broadcastTx', false);

        if (err) {
          return setError(err, gettextCatalog.getString('Could not broadcast payment'));
        }

        $scope.close();
      });
    }, 10);
  };

  $scope.getShortNetworkName = function() {
    return $scope.wallet.credentials.networkName.substring(0, 4);
  };

  var updateTxInfo = function(eventName) {
    $scope.wallet.getTx($scope.tx.id, function(err, tx) {
      if (err) {
        if (err.message && err.message == 'Transaction proposal not found' &&
            (eventName == 'transactionProposalRemoved' || eventName == 'TxProposalRemoved')) {
          $scope.tx.removed = true;
          $scope.tx.canBeRemoved = false;
          $scope.tx.pendingForUs = false;
          $scope.$apply();
        }
        return;
      }

      var action = lodash.find(tx.actions, {
        copayerId: $scope.wallet.credentials.copayerId
      });

      $scope.tx = txFormatService.processTx(tx);

      if (!action && tx.status == 'pending')
        $scope.tx.pendingForUs = true;

      $scope.updateCopayerList();
      initActionList();
      $scope.$apply();
    });
  };

  var bwsEvent =  $rootScope.$on('bwsEvent', function(e, walletId, type, n) {
    lodash.each([
        'TxProposalRejectedBy',
        'TxProposalAcceptedBy',
        'transactionProposalRemoved',
        'TxProposalRemoved',
        'NewOutgoingTx',
        'UpdateTx'
    ], function(eventName) {
      if (walletId == $scope.wallet.id && type == eventName) {
        updateTxInfo(eventName);
      }
    });
  });

  $scope.updateCopayerList = function() {
    lodash.map($scope.copayers, function(cp) {
      lodash.each($scope.tx.actions, function(ac) {
        if (cp.id == ac.copayerId) {
          cp.action = ac.type;
        }
      });
    });
  };

  function statusChangeHandler(processName, showName, isOn) {
    $log.debug('statusChangeHandler: ', processName, showName, isOn);
    if (showName) {
      $scope.sendStatus = showName;
    }
  }

  function success() {
    $scope.sendStatus = 'success';
    $scope.$digest();
  }

  $scope.statusChangeHandler = statusChangeHandler;

  $scope.onConfirm = function() {
    $scope.sign(statusChangeHandler);
  };

  $scope.onSuccessConfirm = function() {
    $ionicHistory.nextViewOptions({
      disableAnimate: true
    });
    $scope.close();
  };

  $scope.close = function() {
    bwsEvent();
    $scope.loading = null;
    $scope.txpDetailsModal.hide();
  };
});
<|endoftext|>


<commit_before>'use strict';

angular.module('copayApp.controllers').controller('txpDetailsController', function($scope, $rootScope, $timeout, $interval, $log, ongoingProcess, platformInfo, $ionicScrollDelegate, txFormatService, bwcError, gettextCatalog, lodash, walletService, popupService, $ionicHistory) {
  var isGlidera = $scope.isGlidera;
  var GLIDERA_LOCK_TIME = 6 * 60 * 60;
  var now = Math.floor(Date.now() / 1000);
  var countDown;
  var listeners = [];

  $scope.init = function() {
    $scope.loading = null;
    $scope.isCordova = platformInfo.isCordova;
    $scope.copayerId = $scope.wallet.credentials.copayerId;
    $scope.isShared = $scope.wallet.credentials.n > 1;
    $scope.canSign = $scope.wallet.canSign() || $scope.wallet.isPrivKeyExternal();
    $scope.color = $scope.wallet.color;
    $scope.data = {};
    $scope.hasClick = platformInfo.hasClick;
    $scope.displayAmount = getDisplayAmount($scope.tx.amountStr);
    $scope.displayUnit = getDisplayUnit($scope.tx.amountStr);
    initActionList();
    checkPaypro();
  }

  function getDisplayAmount(amountStr) {
    return amountStr.split(' ')[0];
  }

  function getDisplayUnit(amountStr) {
    return amountStr.split(' ')[1];
  }

  function initActionList() {
    $scope.actionList = [];

    if (!$scope.isShared) return;

    var actionDescriptions = {
      created: gettextCatalog.getString('Proposal Created'),
      accept: gettextCatalog.getString('Accepted'),
      reject: gettextCatalog.getString('Rejected'),
      broadcasted: gettextCatalog.getString('Broadcasted'),
    };

    $scope.actionList.push({
      type: 'created',
      time: $scope.tx.createdOn,
      description: actionDescriptions['created'],
      by: $scope.tx.creatorName
    });

    lodash.each($scope.tx.actions, function(action) {
      $scope.actionList.push({
        type: action.type,
        time: action.createdOn,
        description: actionDescriptions[action.type],
        by: action.copayerName
      });
    });
  };

  function checkPaypro() {
    if ($scope.tx.payProUrl && !platformInfo.isChromeApp) {
      $scope.wallet.fetchPayPro({
        payProUrl: $scope.tx.payProUrl,
      }, function(err, paypro) {
        if (err) return;
        $scope.tx.paypro = paypro;
        paymentTimeControl($scope.tx.paypro.expires);
        $timeout(function() {
          $ionicScrollDelegate.resize();
        }, 10);
      });
    }
  };

  function paymentTimeControl(expirationTime) {
    $scope.paymentExpired = false;
    setExpirationTime();

    countDown = $interval(function() {
      setExpirationTime();
    }, 1000);

    function setExpirationTime() {
      var now = Math.floor(Date.now() / 1000);
      if (now > expirationTime) {
        $scope.paymentExpired = true;
        if (countDown) $interval.cancel(countDown);
        return;
      }
      var totalSecs = expirationTime - now;
      var m = Math.floor(totalSecs / 60);
      var s = totalSecs % 60;
      $scope.expires = ('0' + m).slice(-2) + ":" + ('0' + s).slice(-2);
    };
  };

  $scope.$on('accepted', function(event) {
    $scope.sign();
  });

  // ToDo: use tx.customData instead of tx.message
  if ($scope.tx.message === 'Glidera transaction' && isGlidera) {
    $scope.tx.isGlidera = true;
    if ($scope.tx.canBeRemoved) {
      $scope.tx.canBeRemoved = (Date.now() / 1000 - ($scope.tx.ts || $scope.tx.createdOn)) > GLIDERA_LOCK_TIME;
    }
  }

  var setError = function (err, prefix) {
    $scope.sendStatus = '';
    $scope.loading = false;
    popupService.showAlert(gettextCatalog.getString('Error'), bwcError.msg(err, prefix));
  };

  $scope.sign = function(onSendStatusChange) {
    $scope.loading = true;
    walletService.publishAndSign($scope.wallet, $scope.tx, function(err, txp) {
      $scope.$emit('UpdateTx');
      if (err) return setError(err, gettextCatalog.getString('Could not send payment'));
      success();
    }, onSendStatusChange);
  };

  $scope.reject = function(txp) {
    var title = gettextCatalog.getString('Warning!');
    var msg = gettextCatalog.getString('Are you sure you want to reject this transaction?');
    popupService.showConfirm(title, msg, null, null, function(res) {
      if (res) {
        $scope.loading = true;

        walletService.reject($scope.wallet, $scope.tx, function(err, txpr) {
          if (err)
            return setError(err, gettextCatalog.getString('Could not reject payment'));

          $scope.close();
        });
      }
    });
  };

  $scope.remove = function() {
    var title = gettextCatalog.getString('Warning!');
    var msg = gettextCatalog.getString('Are you sure you want to remove this transaction?');
    popupService.showConfirm(title, msg, null, null, function(res) {
      if (res) {
        ongoingProcess.set('removeTx', true);
        walletService.removeTx($scope.wallet, $scope.tx, function(err) {
          ongoingProcess.set('removeTx', false);

          // Hacky: request tries to parse an empty response
          if (err && !(err.message && err.message.match(/Unexpected/))) {
            $scope.$emit('UpdateTx');
            return setError(err, gettextCatalog.getString('Could not delete payment proposal'));
          }

          $scope.close();
        });
      }
    });
  };

  $scope.broadcast = function(txp) {
    $scope.loading = true;

    $timeout(function() {
      ongoingProcess.set('broadcastTx', true);
      walletService.broadcastTx($scope.wallet, $scope.tx, function(err, txpb) {
        ongoingProcess.set('broadcastTx', false);

        if (err) {
          return setError(err, gettextCatalog.getString('Could not broadcast payment'));
        }

        $scope.close();
      });
    }, 10);
  };

  $scope.getShortNetworkName = function() {
    return $scope.wallet.credentials.networkName.substring(0, 4);
  };

  var updateTxInfo = function(eventName) {
    $scope.wallet.getTx($scope.tx.id, function(err, tx) {
      if (err) {
        if (err.message && err.message == 'Transaction proposal not found' &&
            (eventName == 'transactionProposalRemoved' || eventName == 'TxProposalRemoved')) {
          $scope.tx.removed = true;
          $scope.tx.canBeRemoved = false;
          $scope.tx.pendingForUs = false;
          $scope.$apply();
        }
        return;
      }

      var action = lodash.find(tx.actions, {
        copayerId: $scope.wallet.credentials.copayerId
      });

      $scope.tx = txFormatService.processTx(tx);

      if (!action && tx.status == 'pending')
        $scope.tx.pendingForUs = true;

      $scope.updateCopayerList();
      initActionList();
      $scope.$apply();
    });
  };

  var bwsEvent =  $rootScope.$on('bwsEvent', function(e, walletId, type, n) {
    lodash.each([
        'TxProposalRejectedBy',
        'TxProposalAcceptedBy',
        'transactionProposalRemoved',
        'TxProposalRemoved',
        'NewOutgoingTx',
        'UpdateTx'
    ], function(eventName) {
      if (walletId == $scope.wallet.id && type == eventName) {
        updateTxInfo(eventName);
      }
    });
  });

  $scope.updateCopayerList = function() {
    lodash.map($scope.copayers, function(cp) {
      lodash.each($scope.tx.actions, function(ac) {
        if (cp.id == ac.copayerId) {
          cp.action = ac.type;
        }
      });
    });
  };

  function statusChangeHandler(processName, showName, isOn) {
    $log.debug('statusChangeHandler: ', processName, showName, isOn);
    if (showName) {
      $scope.sendStatus = showName;
    }
  }

  function success() {
    $scope.sendStatus = 'success';
    $scope.$digest();
  }

  $scope.statusChangeHandler = statusChangeHandler;

  $scope.onConfirm = function() {
    $scope.sign(statusChangeHandler);
  };

  $scope.onSuccessConfirm = function() {
    $ionicHistory.nextViewOptions({
      disableAnimate: true
    });
    $scope.close();
  };

  $scope.close = function() {
    bwsEvent();
    $scope.loading = null;
    $scope.txpDetailsModal.hide();
  };
});
<commit_msg>Removes unused variable<commit_after>'use strict';

angular.module('copayApp.controllers').controller('txpDetailsController', function($scope, $rootScope, $timeout, $interval, $log, ongoingProcess, platformInfo, $ionicScrollDelegate, txFormatService, bwcError, gettextCatalog, lodash, walletService, popupService, $ionicHistory) {
  var isGlidera = $scope.isGlidera;
  var GLIDERA_LOCK_TIME = 6 * 60 * 60;
  var now = Math.floor(Date.now() / 1000);
  var countDown;

  $scope.init = function() {
    $scope.loading = null;
    $scope.isCordova = platformInfo.isCordova;
    $scope.copayerId = $scope.wallet.credentials.copayerId;
    $scope.isShared = $scope.wallet.credentials.n > 1;
    $scope.canSign = $scope.wallet.canSign() || $scope.wallet.isPrivKeyExternal();
    $scope.color = $scope.wallet.color;
    $scope.data = {};
    $scope.hasClick = platformInfo.hasClick;
    $scope.displayAmount = getDisplayAmount($scope.tx.amountStr);
    $scope.displayUnit = getDisplayUnit($scope.tx.amountStr);
    initActionList();
    checkPaypro();
  }

  function getDisplayAmount(amountStr) {
    return amountStr.split(' ')[0];
  }

  function getDisplayUnit(amountStr) {
    return amountStr.split(' ')[1];
  }

  function initActionList() {
    $scope.actionList = [];

    if (!$scope.isShared) return;

    var actionDescriptions = {
      created: gettextCatalog.getString('Proposal Created'),
      accept: gettextCatalog.getString('Accepted'),
      reject: gettextCatalog.getString('Rejected'),
      broadcasted: gettextCatalog.getString('Broadcasted'),
    };

    $scope.actionList.push({
      type: 'created',
      time: $scope.tx.createdOn,
      description: actionDescriptions['created'],
      by: $scope.tx.creatorName
    });

    lodash.each($scope.tx.actions, function(action) {
      $scope.actionList.push({
        type: action.type,
        time: action.createdOn,
        description: actionDescriptions[action.type],
        by: action.copayerName
      });
    });
  };

  function checkPaypro() {
    if ($scope.tx.payProUrl && !platformInfo.isChromeApp) {
      $scope.wallet.fetchPayPro({
        payProUrl: $scope.tx.payProUrl,
      }, function(err, paypro) {
        if (err) return;
        $scope.tx.paypro = paypro;
        paymentTimeControl($scope.tx.paypro.expires);
        $timeout(function() {
          $ionicScrollDelegate.resize();
        }, 10);
      });
    }
  };

  function paymentTimeControl(expirationTime) {
    $scope.paymentExpired = false;
    setExpirationTime();

    countDown = $interval(function() {
      setExpirationTime();
    }, 1000);

    function setExpirationTime() {
      var now = Math.floor(Date.now() / 1000);
      if (now > expirationTime) {
        $scope.paymentExpired = true;
        if (countDown) $interval.cancel(countDown);
        return;
      }
      var totalSecs = expirationTime - now;
      var m = Math.floor(totalSecs / 60);
      var s = totalSecs % 60;
      $scope.expires = ('0' + m).slice(-2) + ":" + ('0' + s).slice(-2);
    };
  };

  $scope.$on('accepted', function(event) {
    $scope.sign();
  });

  // ToDo: use tx.customData instead of tx.message
  if ($scope.tx.message === 'Glidera transaction' && isGlidera) {
    $scope.tx.isGlidera = true;
    if ($scope.tx.canBeRemoved) {
      $scope.tx.canBeRemoved = (Date.now() / 1000 - ($scope.tx.ts || $scope.tx.createdOn)) > GLIDERA_LOCK_TIME;
    }
  }

  var setError = function (err, prefix) {
    $scope.sendStatus = '';
    $scope.loading = false;
    popupService.showAlert(gettextCatalog.getString('Error'), bwcError.msg(err, prefix));
  };

  $scope.sign = function(onSendStatusChange) {
    $scope.loading = true;
    walletService.publishAndSign($scope.wallet, $scope.tx, function(err, txp) {
      $scope.$emit('UpdateTx');
      if (err) return setError(err, gettextCatalog.getString('Could not send payment'));
      success();
    }, onSendStatusChange);
  };

  $scope.reject = function(txp) {
    var title = gettextCatalog.getString('Warning!');
    var msg = gettextCatalog.getString('Are you sure you want to reject this transaction?');
    popupService.showConfirm(title, msg, null, null, function(res) {
      if (res) {
        $scope.loading = true;

        walletService.reject($scope.wallet, $scope.tx, function(err, txpr) {
          if (err)
            return setError(err, gettextCatalog.getString('Could not reject payment'));

          $scope.close();
        });
      }
    });
  };

  $scope.remove = function() {
    var title = gettextCatalog.getString('Warning!');
    var msg = gettextCatalog.getString('Are you sure you want to remove this transaction?');
    popupService.showConfirm(title, msg, null, null, function(res) {
      if (res) {
        ongoingProcess.set('removeTx', true);
        walletService.removeTx($scope.wallet, $scope.tx, function(err) {
          ongoingProcess.set('removeTx', false);

          // Hacky: request tries to parse an empty response
          if (err && !(err.message && err.message.match(/Unexpected/))) {
            $scope.$emit('UpdateTx');
            return setError(err, gettextCatalog.getString('Could not delete payment proposal'));
          }

          $scope.close();
        });
      }
    });
  };

  $scope.broadcast = function(txp) {
    $scope.loading = true;

    $timeout(function() {
      ongoingProcess.set('broadcastTx', true);
      walletService.broadcastTx($scope.wallet, $scope.tx, function(err, txpb) {
        ongoingProcess.set('broadcastTx', false);

        if (err) {
          return setError(err, gettextCatalog.getString('Could not broadcast payment'));
        }

        $scope.close();
      });
    }, 10);
  };

  $scope.getShortNetworkName = function() {
    return $scope.wallet.credentials.networkName.substring(0, 4);
  };

  var updateTxInfo = function(eventName) {
    $scope.wallet.getTx($scope.tx.id, function(err, tx) {
      if (err) {
        if (err.message && err.message == 'Transaction proposal not found' &&
            (eventName == 'transactionProposalRemoved' || eventName == 'TxProposalRemoved')) {
          $scope.tx.removed = true;
          $scope.tx.canBeRemoved = false;
          $scope.tx.pendingForUs = false;
          $scope.$apply();
        }
        return;
      }

      var action = lodash.find(tx.actions, {
        copayerId: $scope.wallet.credentials.copayerId
      });

      $scope.tx = txFormatService.processTx(tx);

      if (!action && tx.status == 'pending')
        $scope.tx.pendingForUs = true;

      $scope.updateCopayerList();
      initActionList();
      $scope.$apply();
    });
  };

  var bwsEvent =  $rootScope.$on('bwsEvent', function(e, walletId, type, n) {
    lodash.each([
        'TxProposalRejectedBy',
        'TxProposalAcceptedBy',
        'transactionProposalRemoved',
        'TxProposalRemoved',
        'NewOutgoingTx',
        'UpdateTx'
    ], function(eventName) {
      if (walletId == $scope.wallet.id && type == eventName) {
        updateTxInfo(eventName);
      }
    });
  });

  $scope.updateCopayerList = function() {
    lodash.map($scope.copayers, function(cp) {
      lodash.each($scope.tx.actions, function(ac) {
        if (cp.id == ac.copayerId) {
          cp.action = ac.type;
        }
      });
    });
  };

  function statusChangeHandler(processName, showName, isOn) {
    $log.debug('statusChangeHandler: ', processName, showName, isOn);
    if (showName) {
      $scope.sendStatus = showName;
    }
  }

  function success() {
    $scope.sendStatus = 'success';
    $scope.$digest();
  }

  $scope.statusChangeHandler = statusChangeHandler;

  $scope.onConfirm = function() {
    $scope.sign(statusChangeHandler);
  };

  $scope.onSuccessConfirm = function() {
    $ionicHistory.nextViewOptions({
      disableAnimate: true
    });
    $scope.close();
  };

  $scope.close = function() {
    bwsEvent();
    $scope.loading = null;
    $scope.txpDetailsModal.hide();
  };
});
<|endoftext|>


<commit_before>//
// Tools for performance debugging, only enabled in development mode.
// Open up Chrome Dev Tools, then Timeline, then User Timing to see output.
// Also see config/webpack/loaders/mark.js for the webpack loader marks.
//

let marky;

if (process.env.NODE_ENV === 'development') {
  marky = require('marky');
  require('react-addons-perf').start();
}

export function start(name) {
  if (process.env.NODE_ENV === 'development') {
    marky.mark(name);
  }
}

export function stop(name) {
  if (process.env.NODE_ENV === 'development') {
    marky.stop(name);
  }
}
<commit_msg>Fix Firefox issue with performance marks (#3315)<commit_after>//
// Tools for performance debugging, only enabled in development mode.
// Open up Chrome Dev Tools, then Timeline, then User Timing to see output.
// Also see config/webpack/loaders/mark.js for the webpack loader marks.
//

let marky;

if (process.env.NODE_ENV === 'development') {
  if (typeof performance !== 'undefined' && performance.setResourceTimingBufferSize) {
    // Increase Firefox's performance entry limit; otherwise it's capped to 150.
    // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1331135
    performance.setResourceTimingBufferSize(Infinity);
  }
  marky = require('marky');
  require('react-addons-perf').start();
}

export function start(name) {
  if (process.env.NODE_ENV === 'development') {
    marky.mark(name);
  }
}

export function stop(name) {
  if (process.env.NODE_ENV === 'development') {
    marky.stop(name);
  }
}
<|endoftext|>


<commit_before>"use strict";

angular.module('arethusa.core').service('globalSettings', [
  'configurator',
  'plugins',
  '$injector',
  '$rootScope',
  function(configurator,  plugins, $injector, $rootScope) {
    var self = this;

    self.settings = {};
    self.colorizers = { disabled: true };

    var confKeys = [
      "alwaysDeselect",
      "colorizer"
    ];

    self.defaultConf = {
      alwaysDeselect: false,
      colorizer: 'morph'
    };

    function configure() {
      self.conf = configurator.configurationFor('main').globalSettings || {};
      configurator.delegateConf(self, confKeys, true); // true makes them sticky

      defineSettings();
    }

    function Conf(property, type, directive, label) {
      this.property = property;
      this.label = label || "globalSettings." + property;
      this.type = type || 'checkbox';
      this.directive = directive;
    }

    function defineSettings() {
      self.defineSetting('alwaysDeselect');
      self.defineSetting('keyboardMappings');
      self.defineSetting('colorizer', 'custom', 'colorizer-setting');
      self.defineSetting('layout', 'custom', 'layout-setting');
    }

    this.defineSetting = function(property, type, directive, label) {
      self.settings[property] = new Conf(property, type, directive, label);
    };

    this.removeSetting = function(setting) {
      delete self.settings[setting];
    };

    this.toggle = function() {
      self.active = !self.active;
    };

    this.addColorizer = function(pluginName) {
      self.colorizers[pluginName] = true;
    };

    this.isColorizer = function(pluginName) {
      return self.colorizer === pluginName;
    };

    function state() {
      return $injector.get('state');
    }

    this.applyColorizer = function() {
      if (self.colorizer === 'disabled') {
        state().unapplyStylings();
      } else {
        // Check if the colorizer is really present
        if (self.colorizers[self.colorizer]) {
          plugins.get(self.colorizer).applyStyling();
        }
      }
    };

    this.colorMaps = function() {
      return arethusaUtil.inject({}, self.colorizers, function(memo, name, _) {
        if (name !== 'disabled') {
          memo[name] = plugins.get(name).colorMap();
        }
      });
    };

    function setLayout() {
      self.layout = configurator.configurationFor('main').template;
    }

    // When Arethusa is used as widget, it's imperative to wait
    // for this event.
    $rootScope.$on('confLoaded', setLayout);

    this.layouts = {
      'templates/main_with_sidepanel.html': 'Sidepanel',
      'templates/main_grid.html': 'Grid'
    };

    this.addLayout = function(name, url) {
      self.layout[name] = url;
    };

    this.init = function() {
      configure();
    };

  }
]);
<commit_msg>Broadcast layout name on change<commit_after>"use strict";

angular.module('arethusa.core').service('globalSettings', [
  'configurator',
  'plugins',
  '$injector',
  '$rootScope',
  function(configurator,  plugins, $injector, $rootScope) {
    var self = this;

    self.settings = {};
    self.colorizers = { disabled: true };

    var confKeys = [
      "alwaysDeselect",
      "colorizer"
    ];

    self.defaultConf = {
      alwaysDeselect: false,
      colorizer: 'morph'
    };

    function configure() {
      self.conf = configurator.configurationFor('main').globalSettings || {};
      configurator.delegateConf(self, confKeys, true); // true makes them sticky

      defineSettings();
    }

    function Conf(property, type, directive, label) {
      this.property = property;
      this.label = label || "globalSettings." + property;
      this.type = type || 'checkbox';
      this.directive = directive;
    }

    function defineSettings() {
      self.defineSetting('alwaysDeselect');
      self.defineSetting('keyboardMappings');
      self.defineSetting('colorizer', 'custom', 'colorizer-setting');
      self.defineSetting('layout', 'custom', 'layout-setting');
    }

    this.defineSetting = function(property, type, directive, label) {
      self.settings[property] = new Conf(property, type, directive, label);
    };

    this.removeSetting = function(setting) {
      delete self.settings[setting];
    };

    this.toggle = function() {
      self.active = !self.active;
    };

    this.addColorizer = function(pluginName) {
      self.colorizers[pluginName] = true;
    };

    this.isColorizer = function(pluginName) {
      return self.colorizer === pluginName;
    };

    function state() {
      return $injector.get('state');
    }

    this.applyColorizer = function() {
      if (self.colorizer === 'disabled') {
        state().unapplyStylings();
      } else {
        // Check if the colorizer is really present
        if (self.colorizers[self.colorizer]) {
          plugins.get(self.colorizer).applyStyling();
        }
      }
    };

    this.colorMaps = function() {
      return arethusaUtil.inject({}, self.colorizers, function(memo, name, _) {
        if (name !== 'disabled') {
          memo[name] = plugins.get(name).colorMap();
        }
      });
    };

    function setLayout() {
      self.layout = configurator.configurationFor('main').template;
    }

    // When Arethusa is used as widget, it's imperative to wait
    // for this event.
    $rootScope.$on('confLoaded', setLayout);

    this.broadcastLayoutChange = function() {
      $rootScope.$broadcast('layoutChange', self.layouts[self.layout]);
    };

    this.layouts = {
      'templates/main_with_sidepanel.html': 'Sidepanel',
      'templates/main_grid.html': 'Grid'
    };

    this.addLayout = function(name, url) {
      self.layout[name] = url;
    };

    this.init = function() {
      configure();
    };

  }
]);
<|endoftext|>


<commit_before>soundcloudPlayer.ng.controller('playerController', ['$scope', '$http', '$log', function ($scope, $http, $log) {
	$log.info('[playerController] Controller Loaded');

	$scope.clientId = '6aeca16577b6acfd3e76ab35ac241d81';
	$scope.url = 'http://api.soundcloud.com/tracks.json?q=';
	$scope.trackList = [];
	$scope.data = {};
	$scope.currentIndex = 0;

	$scope.formSubmitted = false;
	$scope.formSuccess = false;

	$scope.init = (function () {
		$log.info('[player-controller] initialized');

		SC.initialize({
			client_id: $scope.clientId
		});

	})();

	$scope.submitForm = function () {
		$scope.formSubmitted = true;
		$scope.encodedString = encodeURI($scope.category);
		$scope.querySoundcloud();
	};

	$scope.querySoundcloud = function () {

		$http.jsonp($scope.url + $scope.encodedString + '&client_id=' + $scope.clientId + '&callback=JSON_CALLBACK')
		.success(function (data) {

			$scope.formSuccess = true;
			$scope.data = data;

			$scope.data.forEach (function (track) {
				$scope.trackList.push({
					title: track.title,
					uri: track.uri + '/stream?client_id=' + $scope.clientId
				})
			});
			$scope.addTrackToPlayer();

		});
	};

	$scope.addTrackToPlayer = function () {
		var audioPlayer = document.getElementsByTagName('audio');
		audioPlayer[0].setAttribute('src', $scope.trackList[$scope.currentIndex].uri);
		audioPlayer[0].play();

		audioPlayer[0].addEventListener('error', function (e) {
			audioPlayer[0].setAttribute('src', $scope.trackList[++$scope.currentIndex].uri);
			audioPlayer[0].play();
		});
	};

	$scope.updateIndex = function () {
		$scope.currentIndex ++;
	}

}]);
<commit_msg>switched to unshift on array<commit_after>soundcloudPlayer.ng.controller('playerController', ['$scope', '$http', '$log', function ($scope, $http, $log) {
	$log.info('[playerController] Controller Loaded');

	$scope.clientId = '6aeca16577b6acfd3e76ab35ac241d81';
	$scope.url = 'http://api.soundcloud.com/tracks.json?q=';
	$scope.trackList = [];
	$scope.data = {};
	$scope.currentIndex = 0;

	$scope.formSubmitted = false;
	$scope.formSuccess = false;

	$scope.init = (function () {
		$log.info('[player-controller] initialized');

		SC.initialize({
			client_id: $scope.clientId
		});

	})();

	$scope.submitForm = function () {
		$log.info('submitted Form');
		$scope.formSubmitted = true;
		$scope.encodedString = encodeURI($scope.category);

		$scope.querySoundcloud();
	};

	$scope.querySoundcloud = function () {

		$log.info('queried');

		$http.jsonp($scope.url + $scope.encodedString + '&client_id=' + $scope.clientId + '&callback=JSON_CALLBACK')
		.success(function (data) {

			$scope.formSuccess = true;
			$scope.data = data;

			$scope.data.forEach (function (track) {
				var uri = track.uri + '/stream?client_id=' + $scope.clientId;

				$scope.trackList.unshift({
					title: track.title,
					uri: uri
				});
			});

			$scope.addTrackToPlayer();

		});
	};

	$scope.addTrackToPlayer = function () {
		var audioPlayer = document.getElementsByTagName('audio')[0];
		audioPlayer.setAttribute('src', $scope.trackList[$scope.currentIndex].uri);
		audioPlayer.play();

		audioPlayer.addEventListener('error', function (e) {
			audioPlayer.setAttribute('src', $scope.trackList[++$scope.currentIndex].uri);
			audioPlayer.play();
		});
	};

	$scope.updateIndex = function () {
		$scope.currentIndex ++;
	}

}]);
<|endoftext|>


<commit_before>'use strict';
/**
 * @ngdoc controller
 * @name lcaApp.LCIA.comparison:LciaComparisonController
 * @description
 * Controller for LCIA comparison view
 */
angular.module("lcaApp.LCIA.comparison",
    ["ui.router", "lcaApp.resources.service", "lcaApp.status.service", "ngGrid", "lcaApp.plot", "lcaApp.format",
        "lcaApp.models.lcia", "lcaApp.models.scenario"])
    .controller("LciaComparisonController",
    ["$scope", "$stateParams", "$state", "StatusService", "$q", "PlotService", "FormatService",
        "FragmentService", "LciaMethodService", "ProcessService",
        "ScenarioModelService", "LciaModelService",
        function ($scope, $stateParams, $state, StatusService, $q, PlotService, FormatService,
                  FragmentService, LciaMethodService, ProcessService,
                  ScenarioModelService, LciaModelService) {

            $scope.selection = createSelectionComponent();
            $scope.gridData = [];
            $scope.gridOpts = createGrid();
            $scope.invalidSelection = invalidSelection();
            $scope.lciaMethods = [];
            $scope.plot = createPlot();
            /**
             * Remove LCIA method. Used to close panel.
             * @param m Method displayed by panel to be closed
             */
            $scope.removeMethod = function(m) {
                var index = $scope.lciaMethods.indexOf(m);
                if (index > -1) {
                    $scope.lciaMethods.splice(index, 1);
                }
            };

            getData();

            function getData() {
                StatusService.startWaiting();
                $q.all([ScenarioModelService.load(), FragmentService.load(), ProcessService.load(),
                    LciaMethodService.load()]).then(
                    displayData, StatusService.handleFailure);
            }

            function displayData() {
                StatusService.stopWaiting();
                $scope.selection.displayData();
                $scope.lciaMethods = LciaMethodService.getAll();
                $scope.plot.addConfig();
            }

            function addGridRow() {
                var row = {
                        componentType : $scope.selection.type,
                        scenario: $scope.selection.scenario,
                        activityLevel : 1,
                        chartLabel : ($scope.gridData.length + 1).toString(),
                        lciaResults : []
                    };
                if ( $scope.selection.isFragment() ) {
                    row.fragmentID = $scope.selection.fragment.fragmentID;
                    row.componentName = $scope.selection.fragment.name;
                } else {
                    row.processID = $scope.selection.process.processID;
                    row.componentName = $scope.selection.process.getLongName();
                }
                $scope.gridData.push(row);
                // TODO : create function to operate on added row only
                $scope.plot.getResults();
            }

            function invalidSelection() {
                // TODO - implement this validation
                return false;
            }

            function createSelectionComponent() {
                var selection = {
                    type: "Fragment",
                    fragment: null,
                    fragmentOptions: [],
                    process: null,
                    processOptions: [],
                    scenario: null,
                    scenarioOptions: [],
                    add : addGridRow,
                    displayData : displayData,
                    isFragment : isFragment
                };

                return selection;

                function displayData() {
                    selection.fragmentOptions = FragmentService.getAll();
                    selection.processOptions = ProcessService.getAll();
                    selection.scenarioOptions = ScenarioModelService.getAll();
                    if (selection.scenarioOptions.length) {
                        selection.scenario = ScenarioModelService.getActiveScenario();
                        if (selection.fragmentOptions.length) {
                            selection.fragment = FragmentService.get(selection.scenario.topLevelFragmentID);
                        }
                        if (selection.processOptions.length) {
                            selection.processOptions.sort(ProcessService.compareByName);
                            selection.process = selection.processOptions[0];
                        }
                    }
                }

                function isFragment() {
                    return selection.type === "Fragment";
                }
            }

            function createGrid() {
                var columnDefs = [
                        { field: "componentType", displayName: "Component Type", enableCellEdit: false},
                        { field: "componentName", displayName: "Component Name", enableCellEdit: false},
                        { field: "scenario.name", displayName: "Scenario", enableCellEdit: false},
                        { field: "activityLevel", displayName: "Activity Level", enableCellEdit: true},
                        { field: "chartLabel", displayName: "Chart Label", enableCellEdit: true}
                ];

                return {
                    columnDefs : columnDefs,
                    data: "gridData",
                    enableRowSelection: false,
                    enableCellEditOnFocus: true,
                    enableHighlighting: true,
                    enableColumnResize: true,
                    plugins: [new ngGridFlexibleHeightPlugin()]
                };
            }

            function createPlot() {
                var plot = { data: null, config: null};

                plot.addConfig = addConfig;

                plot.getResults = function () {
                    StatusService.startWaiting();
                    var promises = $scope.gridData.map(getLciaResults);
                    $q.all(promises)
                        .then(plotData, StatusService.handleFailure);
                };

                function addConfig() {
                    var config = {};

                    $scope.lciaMethods.forEach( function (m) {
                        var mc = createCommonConfig();
                        mc.content().color(m.getDefaultColor());
                        config[m.lciaMethodID] = mc;
                    });
                    plot.config = config;
                }

                function getX(d) {
                    return d.x;
                }

                function getY(d) {
                    return d.y;
                }

                function createCommonConfig() {
                    var xAxis = PlotService.createAxis(),
                        xDim = PlotService.createDimension()
                            .scale("linear")
                            .valueFn(getX)
                            .axis(xAxis.orientation("bottom").offset(20)),
                        yAxis = PlotService.createAxis(),
                        yDim = PlotService.createDimension()
                            .scale("ordinal")
                            .valueFn(getY)
                            .axis(yAxis.offset(20)),
                        margin = PlotService.createMargin(0, 15);

                    return PlotService.createInstance()
                        .content(PlotService.createBar())
                        .margin(margin)
                            .x(xDim)
                            .y(yDim);
                }

                function getLciaResults(gridRow) {
                    var promise = gridRow.hasOwnProperty("fragmentID") ?
                            LciaModelService
                                .load({
                                    scenarioID: gridRow.scenario.scenarioID,
                                    fragmentID: gridRow.fragmentID
                                }) :
                            LciaModelService
                                .load({
                                    scenarioID: gridRow.scenario.scenarioID,
                                    processID: gridRow.processID
                                });
                    promise.then( function (results) {
                        gridRow.lciaResults = results;
                    });
                    return promise;
                }

                /**
                 * Populate plot data.
                 * Multiply total by activity level.
                 * Store in associative array indexed by lciaMethodID.
                 */
                function plotData() {
                    StatusService.stopWaiting();
                    var data = {};
                    /**
                     * @param {{ chartLabel : string, activityLevel : string | number, lciaResults : [] }} gridRow
                     */
                    $scope.gridData.forEach(function (gridRow) {
                        /**
                         * @param {{ lciaMethodID : number, scenarioID: number, total : number }} result
                         */
                        gridRow.lciaResults.forEach(function (result) {
                            var plotRow = { y: gridRow.chartLabel };
                            if (!data.hasOwnProperty(result.lciaMethodID.toString())) data[result.lciaMethodID] = [];
                            plotRow.x = result.total * +gridRow.activityLevel;
                            data[result.lciaMethodID].push(plotRow);
                        });

                    });
                    plot.data = data;
                }

                return plot;
            }
        }]);<commit_msg>LCIA Comparison: handle empty lciaresults from web api (#8)<commit_after>'use strict';
/**
 * @ngdoc controller
 * @name lcaApp.LCIA.comparison:LciaComparisonController
 * @description
 * Controller for LCIA comparison view
 */
angular.module("lcaApp.LCIA.comparison",
    ["ui.router", "lcaApp.resources.service", "lcaApp.status.service", "ngGrid", "lcaApp.plot", "lcaApp.format",
        "lcaApp.models.lcia", "lcaApp.models.scenario"])
    .controller("LciaComparisonController",
    ["$scope", "$stateParams", "$state", "StatusService", "$q", "PlotService", "FormatService",
        "FragmentService", "LciaMethodService", "ProcessService",
        "ScenarioModelService", "LciaModelService",
        function ($scope, $stateParams, $state, StatusService, $q, PlotService, FormatService,
                  FragmentService, LciaMethodService, ProcessService,
                  ScenarioModelService, LciaModelService) {

            $scope.selection = createSelectionComponent();
            $scope.gridData = [];
            $scope.gridOpts = createGrid();
            $scope.invalidSelection = invalidSelection();
            $scope.lciaMethods = [];
            $scope.plot = createPlot();
            /**
             * Remove LCIA method. Used to close panel.
             * @param m Method displayed by panel to be closed
             */
            $scope.removeMethod = function(m) {
                var index = $scope.lciaMethods.indexOf(m);
                if (index > -1) {
                    $scope.lciaMethods.splice(index, 1);
                }
            };

            getData();

            function getData() {
                StatusService.startWaiting();
                $q.all([ScenarioModelService.load(), FragmentService.load(), ProcessService.load(),
                    LciaMethodService.load()]).then(
                    displayData, StatusService.handleFailure);
            }

            function displayData() {
                StatusService.stopWaiting();
                $scope.selection.displayData();
                $scope.lciaMethods = LciaMethodService.getAll();
                $scope.plot.addConfig();
            }

            function addGridRow() {
                var index = $scope.gridData.length,
                    row = {
                        componentType : $scope.selection.type,
                        scenario: $scope.selection.scenario,
                        activityLevel : 1,
                        index : index,
                        chartLabel : (index+1).toString()
                    };
                if ( $scope.selection.isFragment() ) {
                    row.fragmentID = $scope.selection.fragment.fragmentID;
                    row.componentName = $scope.selection.fragment.name;
                } else {
                    row.processID = $scope.selection.process.processID;
                    row.componentName = $scope.selection.process.getLongName();
                }
                $scope.gridData.push(row);
                $scope.plot.getResult(row);
            }

            function invalidSelection() {
                // TODO - implement this validation
                return false;
            }

            function createSelectionComponent() {
                var selection = {
                    type: "Fragment",
                    fragment: null,
                    fragmentOptions: [],
                    process: null,
                    processOptions: [],
                    scenario: null,
                    scenarioOptions: [],
                    add : addGridRow,
                    displayData : displayData,
                    isFragment : isFragment
                };

                return selection;

                function displayData() {
                    selection.fragmentOptions = FragmentService.getAll();
                    selection.processOptions = ProcessService.getAll();
                    selection.scenarioOptions = ScenarioModelService.getAll();
                    if (selection.scenarioOptions.length) {
                        selection.scenario = ScenarioModelService.getActiveScenario();
                        if (selection.fragmentOptions.length) {
                            selection.fragment = FragmentService.get(selection.scenario.topLevelFragmentID);
                        }
                        if (selection.processOptions.length) {
                            selection.processOptions.sort(ProcessService.compareByName);
                            selection.process = selection.processOptions[0];
                        }
                    }
                }

                function isFragment() {
                    return selection.type === "Fragment";
                }
            }

            function createGrid() {
                var columnDefs = [
                        { field: "componentType", displayName: "Component Type", enableCellEdit: false},
                        { field: "componentName", displayName: "Component Name", enableCellEdit: false},
                        { field: "scenario.name", displayName: "Scenario", enableCellEdit: false},
                        { field: "activityLevel", displayName: "Activity Level", enableCellEdit: true},
                        { field: "chartLabel", displayName: "Chart Label", enableCellEdit: true}
                ];

                return {
                    columnDefs : columnDefs,
                    data: "gridData",
                    enableRowSelection: false,
                    enableCellEditOnFocus: true,
                    enableHighlighting: true,
                    enableColumnResize: true,
                    plugins: [new ngGridFlexibleHeightPlugin()]
                };
            }

            function createPlot() {
                var plot = { data: {}, config: null};

                plot.addConfig = addConfig;

                plot.getResult = function (gridRow) {
                    StatusService.startWaiting();
                    getLciaResults(gridRow)
                        .then(function (results) {
                            plotRow(gridRow, results)
                        }, StatusService.handleFailure);
                };

                plot.getResults = function () {
                    StatusService.startWaiting();
                    var promises = $scope.gridData.map(getLciaResults);
                    $q.all(promises)
                        .then(plotData, StatusService.handleFailure);
                };

                function addConfig() {
                    var config = {};

                    $scope.lciaMethods.forEach( function (m) {
                        var mc = createCommonConfig();
                        mc.content().color(m.getDefaultColor());
                        config[m.lciaMethodID] = mc;
                    });
                    plot.config = config;
                }

                function getX(d) {
                    return d.x;
                }

                function getY(d) {
                    return d.y;
                }

                function createCommonConfig() {
                    var xAxis = PlotService.createAxis(),
                        xDim = PlotService.createDimension()
                            .scale("linear")
                            .valueFn(getX)
                            .axis(xAxis.orientation("bottom").offset(20)),
                        yAxis = PlotService.createAxis(),
                        yDim = PlotService.createDimension()
                            .scale("ordinal")
                            .valueFn(getY)
                            .axis(yAxis.offset(20)),
                        margin = PlotService.createMargin(0, 15);

                    return PlotService.createInstance()
                        .content(PlotService.createBar())
                        .margin(margin)
                            .x(xDim)
                            .y(yDim);
                }

                function getLciaResults(gridRow) {
                    return gridRow.hasOwnProperty("fragmentID") ?
                            LciaModelService
                                .load({
                                    scenarioID: gridRow.scenario.scenarioID,
                                    fragmentID: gridRow.fragmentID
                                }) :
                            LciaModelService
                                .load({
                                    scenarioID: gridRow.scenario.scenarioID,
                                    processID: gridRow.processID
                                });
                }

                function newArray(length) {
                    var a = [];
                    while (length-- > 0) {
                        a.push(null);
                    }
                    return a;
                }

                function plotRow(gridRow, results) {
                    if (results.length) {
                        var data = plot.data;
                        StatusService.stopWaiting();
                        /**
                         * @param {{ lciaMethodID : number, scenarioID: number, total : number }} result
                         */
                        results.forEach(function (result) {
                            var plotRow = {y: gridRow.chartLabel};
                            if (!data.hasOwnProperty(result.lciaMethodID.toString())) data[result.lciaMethodID] =
                                newArray($scope.gridData.length);
                            plotRow.x = result.total * +gridRow.activityLevel;
                            data[result.lciaMethodID][gridRow.index] = plotRow;
                        });
                    } else {
                        var msg = "No LCIA results for "
                            + gridRow.componentName + " and " + gridRow.scenario.name;
                        StatusService.handleFailure(msg);
                        $scope.gridData.splice(gridRow.index, 1);
                    }
                }

                /**
                 * Populate plot data.
                 * Multiply total by activity level.
                 * Store in associative array indexed by lciaMethodID.
                 */
                function plotData() {
                    StatusService.stopWaiting();
                    var data = {};
                    /**
                     * @param {{ chartLabel : string, activityLevel : string | number, lciaResults : [] }} gridRow
                     */
                    $scope.gridData.forEach(function (gridRow) {
                        /**
                         * @param {{ lciaMethodID : number, scenarioID: number, total : number }} result
                         */
                        gridRow.lciaResults.forEach(function (result) {
                            var plotRow = { y: gridRow.chartLabel };
                            if (!data.hasOwnProperty(result.lciaMethodID.toString())) data[result.lciaMethodID] = [];
                            plotRow.x = result.total * +gridRow.activityLevel;
                            data[result.lciaMethodID].push(plotRow);
                        });

                    });
                    plot.data = data;
                }

                return plot;
            }
        }]);<|endoftext|>


<commit_before>class Book {
    constructor(cover, title, author, publisher, year, pages, isbn, bookUrl) {
        this.cover = cover;
        this.title = title;
        this.author = author;
        this.publisher = publisher;
        this.year = year;
        this.pages = pages;
        this.isbn = isbn;
        this.bookUrl = bookUrl;
        this.rating = 0;

        return this;
    }

    get getCover() {
        return this.cover;
    }

    set setCover(value) {
        if (value.match(/\.(jpeg|jpg|gif|png)$/i) !== null) {
            alert('Book cover should be .JPEG, .JPG, .GIF or .PNG');
        } else {
            this.cover = value;
            return this;
        }
    }

    get getTitle() {
        return this.title;
    }

    set setTitle(value) {
        if (value.length < 2 || 60 < value.length) {
            alert('Book title should be in range from 2 to 60 characters');
        } else {
            this.title = value;
            return this;
        }
    }

    get getAuthor() {
        return this.author;
    }

    set setAuthor(value) {
        if (value.length < 2 || 40 < value.length) {
            alert('Book author should be in range from 2 to 40 characters');
        } else {
            this.author = value;
            return this;
        }
    }

    get getPublisher() {
        return this.publisher;
    }

    set setPublisher(value) {
        if (value.length < 2 || 35 < value.length) {
            alert('Book publisher should be in range from 2 to 35 characters');
        } else {
            this.publisher = value;
            return this;
        }
    }

    get getYear() {
        return this.year;
    }

    set setYear(value) {
        if (typeof value !== number || value < 1900 || new Date().getFullYear() < value) {
            alert('Publication year should be a number in range from 1900 to current year');
        } else {
            this.year = value;
            return this;
        }
    }

    get getPages() {
        return this.pages;
    }

    set setPages(value) {
        if (typeof value !== number || value.length < 1 || 3000 < value.length) {
            alert('Book pages should be a number in range from 1 to 3000');
        } else {
            this.pages = value;
            return this;
        }
    }

    get getIsbn() {
        return this.isbn;
    }

    set setIsbn(value) {
        if (typeof value !== number || value.length !== 13) {
            alert('ISBN should be 13 digits');
        } else {
            this.isbn = value;
            return this;
        }
    }

    get getBookUrl() {
        return this.bookUrl;
    }

    set setBookUrl(value) {
        // TODO: Validation
        this.bookUrl = value;
        return this;
    }
}

export default Book;
<commit_msg>Added ISBN number validator<commit_after>class Book {
    constructor(cover, title, author, publisher, year, pages, isbn, bookUrl) {
        this.cover = cover;
        this.title = title;
        this.author = author;
        this.publisher = publisher;
        this.year = year;
        this.pages = pages;
        this.isbn = isbn;
        this.bookUrl = bookUrl;
        this.rating = 0;

        return this;
    }

    get getCover() {
        return this.cover;
    }

    set setCover(value) {
        if (value.match(/\.(jpeg|jpg|gif|png)$/i) !== null) {
            alert('Book cover should be .JPEG, .JPG, .GIF or .PNG');
        } else {
            this.cover = value;
            return this;
        }
    }

    get getTitle() {
        return this.title;
    }

    set setTitle(value) {
        if (value.length < 2 || 60 < value.length) {
            alert('Book title should be in range from 2 to 60 characters');
        } else {
            this.title = value;
            return this;
        }
    }

    get getAuthor() {
        return this.author;
    }

    set setAuthor(value) {
        if (value.length < 2 || 40 < value.length) {
            alert('Book author should be in range from 2 to 40 characters');
        } else {
            this.author = value;
            return this;
        }
    }

    get getPublisher() {
        return this.publisher;
    }

    set setPublisher(value) {
        if (value.length < 2 || 35 < value.length) {
            alert('Book publisher should be in range from 2 to 35 characters');
        } else {
            this.publisher = value;
            return this;
        }
    }

    get getYear() {
        return this.year;
    }

    set setYear(value) {
        if (typeof value !== number || value < 1900 || new Date().getFullYear() < value) {
            alert('Publication year should be a number in range from 1900 to current year');
        } else {
            this.year = value;
            return this;
        }
    }

    get getPages() {
        return this.pages;
    }

    set setPages(value) {
        if (typeof value !== number || value.length < 1 || 3000 < value.length) {
            alert('Book pages should be a number in range from 1 to 3000');
        } else {
            this.pages = value;
            return this;
        }
    }

    get getIsbn() {
        return this.isbn;
    }

    set setIsbn(value) {
        if (!isbnValidator(value)) {
            alert('ISBN should be 13 digits');
        } else {
            this.isbn = value;
            return this;
        }
    }

    get getBookUrl() {
        return this.bookUrl;
    }

    set setBookUrl(value) {
        // TODO: Validation
        this.bookUrl = value;
        return this;
    }

    function isbnValidator(value){
        var isbnValue = value.toString().toLowerCase().replace(/-/g, "");

        if(isbnValue.match(/[a-z]/i) || isbnValue.length != 13 && isbnValue.length != 10) {
            console.log(isbnValue.length);
            return false;
        }
        if(isbnValue.length == 10){
            isbnValue = "978" + isbnValue;
        }

        var i,
            mod,
            sum = 0;
        len = isbnValue.length-1;
        for(i = 0;  i < len; i+=1){
            if(i%2 == 0){
                sum += parseInt(isbnValue.charAt(i), 10)*1;
            } else {
                sum += parseInt(isbnValue.charAt(i), 10)*3;
            }
        }

        mod = sum%10;
        if(10 - mod !== parseInt(isbnValue.charAt(12))){
            return false;
        }

        return true;
    };
}

export default Book;
<|endoftext|>