<commit_before>'use strict';

function createUrl(path) {
    return 'http://localhost:3000/dfl201547282206' + path;
}

function DashboardCtrl($scope, $http) {
    var url = createUrl('/jobs');
    $http.get(url).success(function(jobs) {
        $scope.jobs = jobs;
    });

}
DashboardCtrl.$inject = ['$scope','$http'];

function CreateJobCtrl($scope, $http) {
    $scope.title = 'Create new job';

    console.log("CreateJobCtrl");
    $scope.job = {
        "client":"",
        "address":"",
        "budget":0
    };

    $scope.save = function() {
        var url = createUrl('/jobs/-1');
        $http.post(url, $scope.job)
        .success(function(data, status) {
            console.log('job saved d:' + data + ' id '+ data.id + ' s:' + status);

            $scope.location.path('edit-job/' + data.id);
        })
        .error(function(data, status) {
            $scope.data = data || "Request failed";
            $scope.status = status;
        });
    };
}

CreateJobCtrl.$inject = ['$scope','$http'];

function PrintJobCtrl($scope, JobService, $http, $routeParams) {
    $scope.jobId = $routeParams.jobId;

    var url = createUrl('/jobs/' + $scope.jobId);
    $http.get(url).success(function(job) {
        $scope.job = job;
        $scope.userProfile = job.quotes[0];
    });
}

function ViewContractCtrl($scope, ContractService, $routeParams,QuoteService) {
    var $log = $scope.$log;

    $scope.title = 'View contract';
    $scope.quoteId = $routeParams.quoteId;
    $scope.$log.info("ViewContractCtrl " + $scope.quoteId);
    $scope.editing = {};
    $scope.predicate = '-rowNumber';

    ContractService.find($scope.quoteId).then(function(result) {
        $scope.$log.info("found " + result.data.id);
        var quote = result.data;
        $scope.contract = userProfile.contract;
        $scope.userProfile = userProfile;
        $scope.editing = userProfile;
        $scope.printQuote = function() {
            $log.info("ViewContractCtrl printQuote : " + $scope.userProfile.id);
            QuoteService.openPrint($scope.userProfile);
        }

        $scope.updateQuoteActions($scope);

    });
}

ViewContractCtrl.$inject = ['$scope', 'ContractService', '$routeParams','QuoteService'];

function SettingsCtrl($scope, $http, QuoteService) {
    var $log = $scope.$log;
    console.log("SettingsCtrl");

    $scope.business = {
    };

    $scope.remove = function(idx) {
        $scope.utils.remove($scope.template.sections, idx);
    }

    $scope.add = function(idx) {
        idx = $scope.utils.add($scope.template.sections, {content:''}, idx);
        $scope.focus('content_' + idx);
    }

    $scope.down = function(idx) {
        $scope.utils.down($scope.template.sections, idx);
    }

    $scope.up = function(idx) {
        $scope.utils.up($scope.template.sections, idx);
    }

    $scope.addNewChargeRate = function() {
        $log.info('addNewChargeRate' );
        $scope.business.chargeRates.push({
            labour:'',
            rate:0
        });
    }

    $scope.addNewItemType = function() {
        $log.info('addNewItemType' );
        $scope.business.itemTypes.push({
            type:'',
            margin:0.1
        });
    }

    $scope.save = function() {
        var profile = {};
        profile.business = $scope.business;
        profile.template = $scope.template;
        $http.put('/restlet/business/' + $scope.userProfile.businessId, profile)
        .success(function(data, status) {
            console.log('settings saved d:' + data + ' s:' + status);
            $scope.status = status;
            $scope.data = data;
            $scope.result = data; // Show result from server in our

 element
            $scope.loadUserProfile();
        })
        .error(function(data, status) {
            $scope.data = data || "Request failed";
            $scope.status = status;
        });
    };

    $scope.$watch('userProfile.businessId', function(evt, cur, prev) {
        $http.get('/restlet/business/' + $scope.userProfile.businessId).
        success(function(profile) {

            $scope.business = profile.business;
            $scope.template = profile.template;

            if($scope.business.itemTypes == undefined) {
                $scope.business.itemTypes = [];
                $scope.business.itemTypes.push({
                    type:'Material',
                    margin:0.1
                });
                $scope.business.itemTypes.push({
                    type:'Equipment Hire',
                    margin:0.1
                });
                $scope.business.itemTypes.push({
                    type:'Per square metre',
                    margin:0.1
                });
            }

            if($scope.business.chargeRates == undefined) {
                $scope.business.chargeRates = [];
                $scope.business.chargeRates.push({
                    labour:'Landscape Supervisor',
                    rate:65
                });
                $scope.business.chargeRates.push({
                    labour:'Landscape Tradesperson',
                    rate:65
                });
                $scope.business.chargeRates.push({
                    labour:'Landscape Labourer',
                    rate:45
                });
                $scope.business.chargeRates.push({
                    labour:'Gardern assessment callout fee',
                    rate:250
                });
                $scope.business.chargeRates.push({
                    labour:'Landscape Apprentice 1-2',
                    rate:40
                });
                $scope.business.chargeRates.push({
                    labour:'Landscape Apprentice 3-4',
                    rate:45
                });

                $scope.business.chargeRates.push({
                    labour:'Qualified Horticulturist',
                    rate:65
                });
                $scope.business.chargeRates.push({
                    labour:'Studen/Trainee/Apprentice',
                    rate:50
                });
            }

            if($scope.template == undefined) {
                $scope.template = {};
            }
            if($scope.template.paymentIntro == undefined) {
                $scope.template.paymentIntro = contractDefault.paymentIntro;
            }
            if($scope.template.payments == undefined) {
                $scope.template.payments = contractDefault.payments;
            }
            if(QuoteService.hasContents($scope.template)) {
                $scope.template.sections = contractDefault.sections;
            }
            if($scope.template.intro == undefined) {
                $scope.template.intro = contractDefault.intro;
            }

        });

    });

    var contractDefault = {
        'intro':'I have prepared the following quotation for construction works. All works quoted are based on design and dimensions presented previously and approved by you.',
        'paymentIntro':'If you choose to accept quotation payment is as follows: ',
        'payments':[{
            'title':'Booking deposit',
            'portion':'0.1'
        },

        {
            'title':'On commencement of works',
            'portion':'0.3'
        },

        {
            'title':'Progress payment to be arranged',
            'portion':'0.5'
        },

        {
            'title':'Final payment on final inspection',
            'portion':'0.1'
        },],
        'sections':[{
            content:'On receipt of deposit a start date will be confirmed and schedule of works set. Approved work aside, I will leave the rest of the property in the same state and condition as it was before commencement of landscape works.  I take responsibility for any damage caused by me to the property while undertaking landscape works.'
        },

        {
            content:'Any variations to works and subsequent price variations will be agreed upon by both parties prior to variation.'
        },

        {
            content:'Please note this quote is valid for two months.'
        },

        {
            content:'If you have any questions regarding this quote or works to be undertaken please contact me on 0408122643 or 94170157.'
        },

        {
            content:'I look forward to hearing from you.'
        },

        {
            content:'Kind regards,'
        },

        {
            content:$scope.userProfile.business.owner
        }]
    }

}

SettingsCtrl.$inject = ['$scope','$http', 'QuoteService'];<commit_msg>initial quote integration<commit_after>'use strict';

function createUrl(path) {
    return 'http://localhost:3000/dfl201500062208' + path;
}

function DashboardCtrl($scope, $http) {
    var url = createUrl('/jobs');
    $http.get(url).success(function(jobs) {
        $scope.jobs = jobs;
    });

}
DashboardCtrl.$inject = ['$scope','$http'];

function CreateJobCtrl($scope, $http) {
    $scope.title = 'Create new job';

    console.log("CreateJobCtrl");
    $scope.job = {
        "client":"",
        "address":"",
        "budget":0
    };

    $scope.save = function() {
        var url = createUrl('/jobs/-1');
        $http.post(url, $scope.job)
        .success(function(data, status) {
            console.log('job saved d:' + data + ' id '+ data.id + ' s:' + status);

            $scope.location.path('edit-job/' + data.id);
        })
        .error(function(data, status) {
            $scope.data = data || "Request failed";
            $scope.status = status;
        });
    };
}

CreateJobCtrl.$inject = ['$scope','$http'];

function PrintJobCtrl($scope, JobService, $http, $routeParams) {
    $scope.jobId = $routeParams.jobId;

    var url = createUrl('/jobs/' + $scope.jobId);
    $http.get(url).success(function(job) {
        $scope.job = job;
        $scope.quotes = job.quotes[0];
    });
}

function ViewContractCtrl($scope, ContractService, $routeParams,QuoteService) {
    var $log = $scope.$log;

    $scope.title = 'View contract';
    $scope.quoteId = $routeParams.quoteId;
    $scope.$log.info("ViewContractCtrl " + $scope.quoteId);
    $scope.editing = {};
    $scope.predicate = '-rowNumber';

    ContractService.find($scope.quoteId).then(function(result) {
        $scope.$log.info("found " + result.data.id);
        var quote = result.data;
        $scope.contract = quotes.contract;
        $scope.quotes = quotes;
        $scope.editing = quotes;
        $scope.printQuote = function() {
            $log.info("ViewContractCtrl printQuote : " + $scope.quotes.id);
            QuoteService.openPrint($scope.quotes);
        }

        $scope.updateQuoteActions($scope);

    });
}

ViewContractCtrl.$inject = ['$scope', 'ContractService', '$routeParams','QuoteService'];

function SettingsCtrl($scope, $http, QuoteService) {
    var $log = $scope.$log;
    console.log("SettingsCtrl");

    $scope.business = {
    };

    $scope.remove = function(idx) {
        $scope.utils.remove($scope.template.sections, idx);
    }

    $scope.add = function(idx) {
        idx = $scope.utils.add($scope.template.sections, {content:''}, idx);
        $scope.focus('content_' + idx);
    }

    $scope.down = function(idx) {
        $scope.utils.down($scope.template.sections, idx);
    }

    $scope.up = function(idx) {
        $scope.utils.up($scope.template.sections, idx);
    }

    $scope.addNewChargeRate = function() {
        $log.info('addNewChargeRate' );
        $scope.business.chargeRates.push({
            labour:'',
            rate:0
        });
    }

    $scope.addNewItemType = function() {
        $log.info('addNewItemType' );
        $scope.business.itemTypes.push({
            type:'',
            margin:0.1
        });
    }

    $scope.save = function() {
        var profile = {};
        profile.business = $scope.business;
        profile.template = $scope.template;
        $http.put('/restlet/business/' + $scope.userProfile.businessId, profile)
        .success(function(data, status) {
            console.log('settings saved d:' + data + ' s:' + status);
            $scope.status = status;
            $scope.data = data;
            $scope.result = data; // Show result from server in our

 element
            $scope.loadUserProfile();
        })
        .error(function(data, status) {
            $scope.data = data || "Request failed";
            $scope.status = status;
        });
    };

    $scope.$watch('userProfile.businessId', function(evt, cur, prev) {
        $http.get('/restlet/business/' + $scope.userProfile.businessId).
        success(function(profile) {

            $scope.business = profile.business;
            $scope.template = profile.template;

            if($scope.business.itemTypes == undefined) {
                $scope.business.itemTypes = [];
                $scope.business.itemTypes.push({
                    type:'Material',
                    margin:0.1
                });
                $scope.business.itemTypes.push({
                    type:'Equipment Hire',
                    margin:0.1
                });
                $scope.business.itemTypes.push({
                    type:'Per square metre',
                    margin:0.1
                });
            }

            if($scope.business.chargeRates == undefined) {
                $scope.business.chargeRates = [];
                $scope.business.chargeRates.push({
                    labour:'Landscape Supervisor',
                    rate:65
                });
                $scope.business.chargeRates.push({
                    labour:'Landscape Tradesperson',
                    rate:65
                });
                $scope.business.chargeRates.push({
                    labour:'Landscape Labourer',
                    rate:45
                });
                $scope.business.chargeRates.push({
                    labour:'Gardern assessment callout fee',
                    rate:250
                });
                $scope.business.chargeRates.push({
                    labour:'Landscape Apprentice 1-2',
                    rate:40
                });
                $scope.business.chargeRates.push({
                    labour:'Landscape Apprentice 3-4',
                    rate:45
                });

                $scope.business.chargeRates.push({
                    labour:'Qualified Horticulturist',
                    rate:65
                });
                $scope.business.chargeRates.push({
                    labour:'Studen/Trainee/Apprentice',
                    rate:50
                });
            }

            if($scope.template == undefined) {
                $scope.template = {};
            }
            if($scope.template.paymentIntro == undefined) {
                $scope.template.paymentIntro = contractDefault.paymentIntro;
            }
            if($scope.template.payments == undefined) {
                $scope.template.payments = contractDefault.payments;
            }
            if(QuoteService.hasContents($scope.template)) {
                $scope.template.sections = contractDefault.sections;
            }
            if($scope.template.intro == undefined) {
                $scope.template.intro = contractDefault.intro;
            }

        });

    });

    var contractDefault = {
        'intro':'I have prepared the following quotation for construction works. All works quoted are based on design and dimensions presented previously and approved by you.',
        'paymentIntro':'If you choose to accept quotation payment is as follows: ',
        'payments':[{
            'title':'Booking deposit',
            'portion':'0.1'
        },

        {
            'title':'On commencement of works',
            'portion':'0.3'
        },

        {
            'title':'Progress payment to be arranged',
            'portion':'0.5'
        },

        {
            'title':'Final payment on final inspection',
            'portion':'0.1'
        },],
        'sections':[{
            content:'On receipt of deposit a start date will be confirmed and schedule of works set. Approved work aside, I will leave the rest of the property in the same state and condition as it was before commencement of landscape works.  I take responsibility for any damage caused by me to the property while undertaking landscape works.'
        },

        {
            content:'Any variations to works and subsequent price variations will be agreed upon by both parties prior to variation.'
        },

        {
            content:'Please note this quote is valid for two months.'
        },

        {
            content:'If you have any questions regarding this quote or works to be undertaken please contact me on 0408122643 or 94170157.'
        },

        {
            content:'I look forward to hearing from you.'
        },

        {
            content:'Kind regards,'
        },

        {
            content:$scope.userProfile.business.owner
        }]
    }

}

SettingsCtrl.$inject = ['$scope','$http', 'QuoteService'];<|endoftext|>


<commit_before>/** @jsx React.DOM */
'use strict';
var React = require('react');
var url = require('url');
var mixins = require('./mixins');

// Hide data from NavBarLayout
var NavBar = React.createClass({
    render: function() {
        var section = url.parse(this.props.href).pathname.split('/', 2)[1] || '';
        return NavBarLayout({
            loadingComplete: this.props.loadingComplete,
            portal: this.props.portal,
            section: section,
            session: this.props.session,
            user_actions: this.props.user_actions,
            href: this.props.href,
        });
    }
});

var NavBarLayout = React.createClass({

    render: function() {
        console.log('render navbar');
        var portal = this.props.portal;
        var section = this.props.section;
        var session = this.props.session;
        var user_actions = this.props.user_actions;
        return (

                        <button type="button" className="navbar-toggle" data-toggle="collapse" data-target="#encode-navbar">
                            Toggle navigation

                        </button>
                        {portal.portal_title}

                        <GlobalSections global_sections={portal.global_sections} section={section} />
                        {this.transferPropsTo(<UserActions />)}
                        {this.transferPropsTo(<Search />)}

        );
    }
});

var GlobalSections = React.createClass({
    render: function() {
        var section = this.props.section;
        var actions = this.props.global_sections.map(function (action) {
            var className = action['class'] || '';
            if (section == action.id) {
                className += ' active';
            }
            return (

                    {action.title}

            );
        });
        return
{actions}
;
    }
});

var Search = React.createClass({
    render: function() {
        var id = url.parse(this.props.href, true);
        var searchTerm = id.query['searchTerm'] || '';
        return (
        	<form className="navbar-form pull-right" action="/search/">

    				<input className="form-control search-query" id="navbar-search" type="text" placeholder="Search ENCODE"
                        ref="searchTerm" name="searchTerm" defaultValue={searchTerm} key={searchTerm} />

    		</form>
        );
    }
});

var UserActions = React.createClass({
    render: function() {
        var session = this.props.session;
        var disabled = !this.props.loadingComplete;
        if (!(session && session['auth.userid'])) {
            return (

Sign in

            );
        }
        var actions = this.props.user_actions.map(function (action) {
            return (

                        {action.title}

            );
        });
        var fullname = (session.user_properties && session.user_properties.title) || 'unknown';
        return (

                    {fullname}

                            {actions}

        );
    }
});

module.exports = NavBar;
<commit_msg>Added roles, etc, for accessibility.<commit_after>/** @jsx React.DOM */
'use strict';
var React = require('react');
var url = require('url');
var mixins = require('./mixins');

// Hide data from NavBarLayout
var NavBar = React.createClass({
    render: function() {
        var section = url.parse(this.props.href).pathname.split('/', 2)[1] || '';
        return NavBarLayout({
            loadingComplete: this.props.loadingComplete,
            portal: this.props.portal,
            section: section,
            session: this.props.session,
            user_actions: this.props.user_actions,
            href: this.props.href,
        });
    }
});

var NavBarLayout = React.createClass({

    render: function() {
        console.log('render navbar');
        var portal = this.props.portal;
        var section = this.props.section;
        var session = this.props.session;
        var user_actions = this.props.user_actions;
        return (

                        <button type="button" className="navbar-toggle" data-toggle="collapse" data-target="#encode-navbar">
                            Toggle navigation

                        </button>
                        {portal.portal_title}

                        <GlobalSections global_sections={portal.global_sections} section={section} />
                        {this.transferPropsTo(<UserActions />)}
                        {this.transferPropsTo(<Search />)}

        );
    }
});

var GlobalSections = React.createClass({
    render: function() {
        var section = this.props.section;
        var actions = this.props.global_sections.map(function (action) {
            var className = action['class'] || '';
            if (section == action.id) {
                className += ' active';
            }
            return (

                    {action.title}

            );
        });
        return
{actions}
;
    }
});

var Search = React.createClass({
    render: function() {
        var id = url.parse(this.props.href, true);
        var searchTerm = id.query['searchTerm'] || '';
        return (
        	<form className="navbar-form pull-right" action="/search/">

    				<input className="form-control search-query" id="navbar-search" type="text" placeholder="Search ENCODE"
                        ref="searchTerm" name="searchTerm" defaultValue={searchTerm} key={searchTerm} />

    		</form>
        );
    }
});

var UserActions = React.createClass({
    render: function() {
        var session = this.props.session;
        var disabled = !this.props.loadingComplete;
        if (!(session && session['auth.userid'])) {
            return (

Sign in

            );
        }
        var actions = this.props.user_actions.map(function (action) {
            return (

                        {action.title}

            );
        });
        var fullname = (session.user_properties && session.user_properties.title) || 'unknown';
        return (

                    {fullname}

                            {actions}

        );
    }
});

module.exports = NavBar;
<|endoftext|>


<commit_before>import fields from '../mutation'
import r from '../../../../../db/connect'
import factory from '../../../../../test/factories'
import Spec from '../../../../../test/helpers/Spec'
import {runGraphQLQuery} from '../../../../../test/graphql-helpers'

class VoteForGoalsSpec extends Spec {

  async given() {
    const state = this.state

    state.chapter = await factory.create('chapter')
    state.cycle = await factory.create('cycle', {chapterId: state.chapter.id, state: 'GOAL_SELECTION'})
    state.player = await factory.create('player', {chapterId: state.chapter.id})

    state.voteGoals = [
      {url: `${state.chapter.goalRepositoryURL}/issues/1`},
      {url: `${state.chapter.goalRepositoryURL}/issues/2`},
    ]
  }

  async when() {
    const {voteGoals, player} = this.state

    return await runGraphQLQuery(
      `{
        voteForGoals(
          goals: ["${voteGoals[0].url}", "${voteGoals[1].url}"],
          playerId: "${player.id}"
        )
        { id }
      }`,
      fields,
      {currentUser: {roles: ['player']}},
    )
  }

  async expect(t) {
    const state = this.state

    state.votes = await state.r.table('votes').run()
    state.vote = state.votes[0]

    t.is(state.vote.cycleId, state.cycle.id)
    t.is(state.vote.playerId, state.player.id)
    t.deepEqual(state.vote.goals, state.voteGoals)

    t.deepEqual(this.result.data.voteForGoals.id, state.vote.id)
  }
}

VoteForGoalsSpec.run({r})

class VoteForGoalsWhenVoteExistsSpec extends VoteForGoalsSpec {
  async given(t) {
    await super.given(t)
    this.state.initialVote = await factory.create('vote', {
      playerId: this.state.player.id,
      cycleId: this.state.cycle.id
    })
  }

  async expect(t) {
    await super.expect(t)
    t.is(this.state.vote.id, this.state.initialVote.id)
    t.not(
      this.state.vote.updatedAt.getTime(),
      this.state.initialVote.updatedAt.getTime()
    )
  }
}

VoteForGoalsWhenVoteExistsSpec.run({r})
<commit_msg>adding test for voting for a different user<commit_after>import fields from '../mutation'
import r from '../../../../../db/connect'
import factory from '../../../../../test/factories'
import Spec from '../../../../../test/helpers/Spec'
import {runGraphQLQuery} from '../../../../../test/graphql-helpers'

class VoteForGoalsSpec extends Spec {

  async given() {
    const state = this.state

    state.chapter = await factory.create('chapter')
    state.cycle = await factory.create('cycle', {chapterId: state.chapter.id, state: 'GOAL_SELECTION'})
    state.player = await factory.create('player', {chapterId: state.chapter.id})

    state.voteGoals = [
      {url: `${state.chapter.goalRepositoryURL}/issues/1`},
      {url: `${state.chapter.goalRepositoryURL}/issues/2`},
    ]
  }

  async when() {
    const {voteGoals, player} = this.state

    return await runGraphQLQuery(
      `{
        voteForGoals(
          goals: ["${voteGoals[0].url}", "${voteGoals[1].url}"],
        )
        { id }
      }`,
      fields,
      {currentUser: {id: player.id, roles: ['player']}},
    )
  }

  async expect(t) {
    const state = this.state

    state.votes = await state.r.table('votes').run()
    state.vote = state.votes[0]

    t.is(state.vote.cycleId, state.cycle.id)
    t.is(state.vote.playerId, state.player.id)
    t.deepEqual(state.vote.goals, state.voteGoals)

    t.deepEqual(this.result.data.voteForGoals.id, state.vote.id)
  }
}

VoteForGoalsSpec.run({r})

class VoteForGoalsWhenVoteExistsSpec extends VoteForGoalsSpec {
  async given(t) {
    await super.given(t)
    this.state.initialVote = await factory.create('vote', {
      playerId: this.state.player.id,
      cycleId: this.state.cycle.id
    })
  }

  async expect(t) {
    await super.expect(t)
    t.is(this.state.vote.id, this.state.initialVote.id)
    t.not(
      this.state.vote.updatedAt.getTime(),
      this.state.initialVote.updatedAt.getTime()
    )
  }
}
VoteForGoalsWhenVoteExistsSpec.run({r})

class VoteForGoalsForAnotherPlayerSpec extends VoteForGoalsSpec {
  async given() {
    await super.given()
    this.state.currentUser = factory.create('player')
  }

  async when() {
    const {voteGoals, player, currentUser} = this.state

    return await runGraphQLQuery(
      `{
        voteForGoals(
          goals: ["${voteGoals[0].url}", "${voteGoals[1].url}"],
          playerId: "${player.id}",
        )
        { id }
      }`,
      fields,
      {currentUser: {id: currentUser.id, roles: ['player']}},
    )
  }
}
VoteForGoalsForAnotherPlayerSpec.run({r})
<|endoftext|>


<commit_before>'use strict';

/* Controllers */

var puntsControllers = angular.module('puntsControllers', []);

phonecatControllers.controller('PhoneListCtrl', ['$scope', 'Phone',
  function($scope, Phone) {
    $scope.phones = Phone.query();
    $scope.orderProp = 'age';
  }]);

phonecatControllers.controller('PhoneDetailCtrl', ['$scope', '$routeParams', 'Phone',
  function($scope, $routeParams, Phone) {
    $scope.phone = Phone.get({phoneId: $routeParams.phoneId}, function(phone) {
      $scope.mainImageUrl = phone.images[0];
    });

    $scope.setImage = function(imageUrl) {
      $scope.mainImageUrl = imageUrl;
    };
  }]);
<commit_msg>Update controllers.js<commit_after>'use strict';

/* Controllers */

var puntsControllers = angular.module('puntsControllers', []);

phonecatControllers.controller('PuntsStatus', ['$scope', 'Punts',
  function($scope, Punts) {
    $scope.punts = Punts.query();
  }]);

phonecatControllers.controller('PhoneDetailCtrl', ['$scope', '$routeParams', 'Phone',
  function($scope, $routeParams, Phone) {
    $scope.phone = Phone.get({phoneId: $routeParams.phoneId}, function(phone) {
      $scope.mainImageUrl = phone.images[0];
    });

    $scope.setImage = function(imageUrl) {
      $scope.mainImageUrl = imageUrl;
    };
  }]);
<|endoftext|>


<commit_before>'use strict';

var peerhandler = angular.module('peerhandler', ['ngCordova' ]);

peerhandler.factory('peerFactory', function($rootScope, $ionicPopup, $ionicHistory, $state, $timeout, $cordovaLocalNotification, audioFactory, contactsFactory) {
    // PeerJS object representing the user
    var me                = null;

    // Stream object;
    var localStream       = null;

    // Stream sources
    var remoteVideoSource = null;
    var localVideoSource  = null;

    // currentCallStream   = local
    // currentAnswerStream = remote
    var currentCallStream   = null;
    var currentAnswerStream = null;

    // Simple boolean flag to check if we're currently in a call
    var isInCallCurrently = false;

    // Seperate data connection for sending data
    var dataConnection = null;

    // Array of callback functions to handle data
    var dataCallbacks   = [];

    // Array of ids for local notifications that are shown to the user
    var notificationIds = [];

    // Get a suitable camera for video (first choice is backwards-facing camera)
    var getDeviceCameraID= function() {
        return new Promise(function(resolve, reject) {
            var first = false;
            var back  = false;

            MediaStreamTrack.getSources(function(src) {
                for (var i in src) {
                    if (src[i].kind === 'video') {

                        if (!first) {
                            first = src[i].id;
                        }

                        if (src[i].facing === 'environment') {
                            back = src[i].id;
                        }

                    }
                }
                resolve(back || first);
            });
        })
    };

    // Private api
    var getLocalStream = function(successCallback) {
        if (localStream && successCallback) {
            successCallback(localStream)
        } else {
            getDeviceCameraID().then(function (id) {
                var videoSettings = {};

                if (id) {
                    videoSettings.optional = [{sourceId: id}];
                }

                navigator.webkitGetUserMedia(
                    {
                        audio: true,
                        video: videoSettings
                    },
                    function (stream) {
                        localStream = stream;
                        setLocalStreamSrc(stream);
                        if (successCallback) {
                            successCallback(stream);
                        }
                    },
                    function(err){
                        console.log(err);
                    }
                )
            })
        }
    };

    var cancelAllLocalNotifications = function() {
            notificationIds.map(function (id) {
                console.log(id);
                cancelLocalNotification(id);
            });
    };

    var cancelLocalNotification = function(id) {
        $cordovaLocalNotification.cancel(id);
        var index = notificationIds.indexOf(id);
        notificationIds.splice(index, 1);
    }

    var setRemoteStreamSrc = function (stream) {
        remoteVideoSource = window.URL.createObjectURL(stream);
    };

    var setLocalStreamSrc = function (stream) {
        localVideoSource = window.URL.createObjectURL(stream);
    };

    // TODO: Make closing dataconnection more modular?
    var checkIfDataConnectionIsSet = function (incomingConnection) {
       if (typeof dataConnnection != 'undefined' || dataConnection != null) {
           console.log('dataconnection formed! closing...');
        incomingConnection.serialization = 'none';
        incomingConnection.reliable = true;

        incomingConnection.on('open', function () {
            console.log('opened bad connection, closing...');
            incomingConnection.send(
                JSON.stringify({
                    type: 'connectionClose',
                    message: 'User is already in call!'
                })
            );
            incomingConnection.close();
            incomingConnection = null;
        })

        return true;
       }
       else {
        return false;
       }
    };

    var setDataConnection = function(dataConn) {
        dataConn.serialization = 'none';
        dataConn.reliable = true;

        dataConn.on('open', function() {
            console.log('Dataconnection opened')
            dataConn.on('data', function(data) {
                console.log('data received!');
                console.log(data);

                var dataJSON = JSON.parse(data);
                if (dataJSON.type == 'connectionClose') {
                    callAlertModal(dataJSON.message);
                }
                else {
                     dataCallbacks.map(function (cb) {
                        cb(data);
                     });
                }
        });

        dataConn.on('error', function(err) {
             console.log('dataconnection error!')
             console.log(err);
             alert(err);
         });

        dataConn.on('close', function() {
            console.log('dataconn closed')
        });

        dataConnection = dataConn;
        })
    };

    var sendData = function(data) {

        if (typeof data != 'string') {
                data = JSON.stringify(data);
         }
        if (dataConnection) {
            dataConnection.send(data);
        }
        else {
            console.log('uhoh! data was null')
        }
    }

    var closeDataConnection = function(reason) {
        console.log('closing data connection')
      if (dataConnection) {
        if (reason) {
            sendData({
                type:    'connectionClose',
                message: reason
            });
        }
        dataConnection.close();
        dataConnection = null;
      }
    };

    var endCallAndGoBack = function() {
        console.log('end call and go back!');
        isInCallCurrently = false;
        endCurrentCall();
        if ($state.current.name === "call") {
            $ionicHistory.goBack();
        }
    };

    var callAlertModal = function(reasonMessage) {
        var callEndedAlert = $ionicPopup.alert({
            title: 'Call over!',
            template: reasonMessage
        });
        callEndedAlert.then(function() {
            endCallAndGoBack();
        });

    };

    var answer = function(call) {
        currentAnswerStream = call;
        call.on('stream', setRemoteStreamSrc);
        call.on('close', function() {
            endCallAndGoBack();
        });

        call.on('error', function(error) {
            callAlertModal('Error: ' + error.toString());
        });

        call.answer(localStream);
    };

    var endCurrentCall = function() {
        cancelAllLocalNotifications();
        closeDataConnection();
        if (currentCallStream) {
            currentCallStream.close();
            currentCallStream = null;
        }

        if (currentAnswerStream) {
            currentAnswerStream.close();
            currentAnswerStream = null;
        }

    };

     var disconnectFromPeerJS = function() {
            console.log("Disconnecting from PeerJS")
         if (me) {
            me.disconnect();
            me.destroy();
            me = null;
            endCurrentCall();
        } else {
            console.log("Warning! Attempted to disconnect without connecting")
        }
    };

    // Public PeerJS api
    return {
        getMe: function() {
            if (!me) {return null;}
            return me;
        },
        getRemoteStreamSrc: function() {
            if (!remoteVideoSource) {
                console.log('No video remote source!');
                return null;
            }
            return remoteVideoSource;
        },
        getLocalStreamSrc: function() {
            if (!localVideoSource) {
                console.log('No video remote source!')
                return null;
            }
            return localVideoSource;
        },
        isConnected: function() {
            if (!me) {return false}
            else {return !me.disconnected}
        },
        addDatacallback: function (callback) {
            dataCallbacks.push(callback);
        },
        removeDatacallbacks: function() {
            console.log('removing data callbacks from peerfactory');
            dataCallbacks = [];
        },
        connectToPeerJS: function(id)   {
            var disconnectRef = this.disconnectFromPeerJS;

            return new Promise(function(resolve, reject) {

                me = new Peer(id, $rootScope.config.peerjs);

                if (!me) {
                    alert('Error creating peer!');
                    reject();
                }
                me.on('open', function(id) {
                    resolve();
                    console.log('Connection opened: ' + id);
                });

                me.on('call', function(mediaConnection) {

                    if (isInCallCurrently === false) {
                        console.log(isInCallCurrently);
                        isInCallCurrently = true;

                        var user = contactsFactory.getContactByNumber(mediaConnection.peer);
                        var id = Math.floor(Math.random() * 10000);
                        notificationIds.push(id);

                        if (!user) {
                            user = { displayName: mediaConnection.peer }
                        }

                        $cordovaLocalNotification.schedule({
                            id: id,
                            title: 'SAR Call from ' + user.displayName + ' (' + mediaConnection.peer + ')',
                            text: 'SAR Call from ' + user.displayName + ' (' + mediaConnection.peer + ')'
                        }).then(function (result) {
                            console.log(result);
                        });

                        var confirmPopup = $ionicPopup.confirm({
                            title: 'Call from ' + user.displayName,
                            template: 'Incoming call. Answer?'
                        });
                        audioFactory.playSound('.call');
                        confirmPopup.then(function(res) {
                            audioFactory.stopSound('.call');
                            cancelLocalNotification(id);
                            if(res) {
                                answer(mediaConnection);
                                $timeout(function() {
                                    $state.go('call', {user: user});
                                }, 500)
                            } else {
                                closeDataConnection('User is busy!');
                                mediaConnection.close();
                                return false;
                            }
                        });
                    } else {
                        console.log('ALready in call, closing media')
                        mediaConnection.close();
                    }
                });

                me.on('connection', function(dataConn) {
                    if (checkIfDataConnectionIsSet(dataConn) === false) {
                        setDataConnection(dataConn);
                    }
                });

                me.on('close', function() {
                    console.log('closed Peerjs connection');
                });

                me.socket._socket.onopen = function() {
                    getLocalStream();
                };

                me.on('error', function(error) {

                    var errorMsg = error.toString();
                    if (_.contains(errorMsg), 'could not connect to peer') {
                        errorMsg = 'Looks like that user is offline!';
                    }

                    var errorAlert = $ionicPopup.alert({
                        title: 'Something went wrong!',
                        template: errorMsg
                    });
                    errorAlert.then(function(res) {
                        if (_.contains(errorMsg, 'Lost connection to server') || _.contains(errorMsg, 'is taken')) {
                            disconnectRef();
                            $state.go('login');
                        }
                    });
                });
            })
        },
        isDataConnectionOpen: function() {
          if (dataConnection === null && dataConnection.open === false) { return false}
          return true;
        },
        sendDataToPeer: function(dataToSend) {
            console.log(typeof dataToSend);
            sendData(dataToSend);
        },
        callPeer: function (userToCall) {
            if (!me) {
                console.log("Warning! no peerjs connection")
            }
            isInCallCurrently = true;
            getLocalStream(function (stream) {

                currentCallStream = me.call(userToCall.number, stream, { metadata: me.id});

                currentCallStream.on('error', function (err) {
                    isInCallCurrently = false;
                    alert(err);
                });

                currentCallStream.on('stream', function(stream) {
                    console.log('going to stream from call')
                    setRemoteStreamSrc(stream);

                    $timeout(function() {
                         $state.go('call', {user: userToCall});
                     }, 500)

                });

                currentCallStream.on('close', function() {
                    endCallAndGoBack();
                });
            });

            var dataConn = me.connect(userToCall.number, {reliable: true, serialization: "none"});

            if (checkIfDataConnectionIsSet(dataConn) === false) {
                setDataConnection(dataConn);
            }
        },
        endCurrentCall: function() {
            console.log('ending current call')
            isInCallCurrently = false;
            endCurrentCall();
        },
        disconnectFromPeerJS: function() {
            disconnectFromPeerJS();
        }
    }
});

<commit_msg>Show loader when calling<commit_after>'use strict';

var peerhandler = angular.module('peerhandler', ['ngCordova' ]);

peerhandler.factory('peerFactory', function($rootScope, $ionicPopup, $ionicLoading, $ionicHistory, $state, $timeout, $cordovaLocalNotification, audioFactory, contactsFactory) {
    // PeerJS object representing the user
    var me                = null;

    // Stream object;
    var localStream       = null;

    // Stream sources
    var remoteVideoSource = null;
    var localVideoSource  = null;

    // currentCallStream   = local
    // currentAnswerStream = remote
    var currentCallStream   = null;
    var currentAnswerStream = null;

    // Simple boolean flag to check if we're currently in a call
    var isInCallCurrently = false;

    // Seperate data connection for sending data
    var dataConnection = null;

    // Array of callback functions to handle data
    var dataCallbacks   = [];

    // Array of ids for local notifications that are shown to the user
    var notificationIds = [];

    // Get a suitable camera for video (first choice is backwards-facing camera)
    var getDeviceCameraID= function() {
        return new Promise(function(resolve, reject) {
            var first = false;
            var back  = false;

            MediaStreamTrack.getSources(function(src) {
                for (var i in src) {
                    if (src[i].kind === 'video') {

                        if (!first) {
                            first = src[i].id;
                        }

                        if (src[i].facing === 'environment') {
                            back = src[i].id;
                        }

                    }
                }
                resolve(back || first);
            });
        })
    };

    // Private api
    var getLocalStream = function(successCallback) {
        if (localStream && successCallback) {
            successCallback(localStream)
        } else {
            getDeviceCameraID().then(function (id) {
                var videoSettings = {};

                if (id) {
                    videoSettings.optional = [{sourceId: id}];
                }

                navigator.webkitGetUserMedia(
                    {
                        audio: true,
                        video: videoSettings
                    },
                    function (stream) {
                        localStream = stream;
                        setLocalStreamSrc(stream);
                        if (successCallback) {
                            successCallback(stream);
                        }
                    },
                    function(err){
                        console.log(err);
                    }
                )
            })
        }
    };

    var cancelAllLocalNotifications = function() {
            notificationIds.map(function (id) {
                console.log(id);
                cancelLocalNotification(id);
            });
    };

    var cancelLocalNotification = function(id) {
        $cordovaLocalNotification.cancel(id);
        var index = notificationIds.indexOf(id);
        notificationIds.splice(index, 1);
    }

    var setRemoteStreamSrc = function (stream) {
        remoteVideoSource = window.URL.createObjectURL(stream);
    };

    var setLocalStreamSrc = function (stream) {
        localVideoSource = window.URL.createObjectURL(stream);
    };

    // TODO: Make closing dataconnection more modular?
    var checkIfDataConnectionIsSet = function (incomingConnection) {
       if (typeof dataConnnection != 'undefined' || dataConnection != null) {
           console.log('dataconnection formed! closing...');
        incomingConnection.serialization = 'none';
        incomingConnection.reliable = true;

        incomingConnection.on('open', function () {
            console.log('opened bad connection, closing...');
            incomingConnection.send(
                JSON.stringify({
                    type: 'connectionClose',
                    message: 'User is already in call!'
                })
            );
            incomingConnection.close();
            incomingConnection = null;
        })

        return true;
       }
       else {
        return false;
       }
    };

    var setDataConnection = function(dataConn) {
        dataConn.serialization = 'none';
        dataConn.reliable = true;

        dataConn.on('open', function() {
            console.log('Dataconnection opened')
            dataConn.on('data', function(data) {
                console.log('data received!');
                console.log(data);

                var dataJSON = JSON.parse(data);
                if (dataJSON.type == 'connectionClose') {
                    callAlertModal(dataJSON.message);
                }
                else {
                     dataCallbacks.map(function (cb) {
                        cb(data);
                     });
                }
        });

        dataConn.on('error', function(err) {
             console.log('dataconnection error!')
             console.log(err);
             alert(err);
         });

        dataConn.on('close', function() {
            console.log('dataconn closed')
        });

        dataConnection = dataConn;
        })
    };

    var sendData = function(data) {

        if (typeof data != 'string') {
                data = JSON.stringify(data);
         }
        if (dataConnection) {
            dataConnection.send(data);
        }
        else {
            console.log('uhoh! data was null')
        }
    }

    var closeDataConnection = function(reason) {
        console.log('closing data connection')
      if (dataConnection) {
        if (reason) {
            sendData({
                type:    'connectionClose',
                message: reason
            });
        }
        dataConnection.close();
        dataConnection = null;
      }
    };

    var endCallAndGoBack = function() {
        console.log('end call and go back!');
        isInCallCurrently = false;
        endCurrentCall();
        if ($state.current.name === "call") {
            $ionicHistory.goBack();
        }
    };

    var callAlertModal = function(reasonMessage) {
        var callEndedAlert = $ionicPopup.alert({
            title: 'Call over!',
            template: reasonMessage
        });
        callEndedAlert.then(function() {
            endCallAndGoBack();
        });

    };

    var answer = function(call) {
        currentAnswerStream = call;
        call.on('stream', setRemoteStreamSrc);
        call.on('close', function() {
            endCallAndGoBack();
        });

        call.on('error', function(error) {
            callAlertModal('Error: ' + error.toString());
        });

        call.answer(localStream);
    };

    var endCurrentCall = function() {
        cancelAllLocalNotifications();
        closeDataConnection();
        if (currentCallStream) {
            currentCallStream.close();
            currentCallStream = null;
        }

        if (currentAnswerStream) {
            currentAnswerStream.close();
            currentAnswerStream = null;
        }

    };

     var disconnectFromPeerJS = function() {
            console.log("Disconnecting from PeerJS")
         if (me) {
            me.disconnect();
            me.destroy();
            me = null;
            endCurrentCall();
        } else {
            console.log("Warning! Attempted to disconnect without connecting")
        }
    };

    // Public PeerJS api
    return {
        getMe: function() {
            if (!me) {return null;}
            return me;
        },
        getRemoteStreamSrc: function() {
            if (!remoteVideoSource) {
                console.log('No video remote source!');
                return null;
            }
            return remoteVideoSource;
        },
        getLocalStreamSrc: function() {
            if (!localVideoSource) {
                console.log('No video remote source!')
                return null;
            }
            return localVideoSource;
        },
        isConnected: function() {
            if (!me) {return false}
            else {return !me.disconnected}
        },
        addDatacallback: function (callback) {
            dataCallbacks.push(callback);
        },
        removeDatacallbacks: function() {
            console.log('removing data callbacks from peerfactory');
            dataCallbacks = [];
        },
        connectToPeerJS: function(id)   {
            var disconnectRef = this.disconnectFromPeerJS;

            return new Promise(function(resolve, reject) {

                me = new Peer(id, $rootScope.config.peerjs);

                if (!me) {
                    alert('Error creating peer!');
                    reject();
                }
                me.on('open', function(id) {
                    resolve();
                    console.log('Connection opened: ' + id);
                });

                me.on('call', function(mediaConnection) {

                    if (isInCallCurrently === false) {
                        console.log(isInCallCurrently);
                        isInCallCurrently = true;

                        var user = contactsFactory.getContactByNumber(mediaConnection.peer);
                        var id = Math.floor(Math.random() * 10000);
                        notificationIds.push(id);

                        if (!user) {
                            user = { displayName: mediaConnection.peer }
                        }

                        $cordovaLocalNotification.schedule({
                            id: id,
                            title: 'SAR Call from ' + user.displayName + ' (' + mediaConnection.peer + ')',
                            text: 'SAR Call from ' + user.displayName + ' (' + mediaConnection.peer + ')'
                        }).then(function (result) {
                            console.log(result);
                        });

                        var confirmPopup = $ionicPopup.confirm({
                            title: 'Call from ' + user.displayName,
                            template: 'Incoming call. Answer?'
                        });
                        audioFactory.playSound('.call');
                        confirmPopup.then(function(res) {
                            audioFactory.stopSound('.call');
                            cancelLocalNotification(id);
                            if(res) {
                                answer(mediaConnection);
                                $timeout(function() {
                                    $state.go('call', {user: user});
                                }, 500)
                            } else {
                                closeDataConnection('User is busy!');
                                mediaConnection.close();
                                return false;
                            }
                        });
                    } else {
                        console.log('ALready in call, closing media')
                        mediaConnection.close();
                    }
                });

                me.on('connection', function(dataConn) {
                    if (checkIfDataConnectionIsSet(dataConn) === false) {
                        setDataConnection(dataConn);
                    }
                });

                me.on('close', function() {
                    console.log('closed Peerjs connection');
                });

                me.socket._socket.onopen = function() {
                    getLocalStream();
                };

                me.on('error', function(error) {
                    console.log('error');
                    $ionicLoading.hide();
                    var errorMsg = error.toString();
                    if (_.contains(errorMsg), 'could not connect to peer') {
                        errorMsg = 'Looks like that user is offline!';
                    }

                    var errorAlert = $ionicPopup.alert({
                        title: 'Something went wrong!',
                        template: errorMsg
                    });
                    errorAlert.then(function(res) {
                        if (_.contains(errorMsg, 'Lost connection to server') || _.contains(errorMsg, 'is taken')) {
                            disconnectRef();
                            $state.go('login');
                        }
                    });
                });
            })
        },
        isDataConnectionOpen: function() {
          if (dataConnection === null && dataConnection.open === false) { return false}
          return true;
        },
        sendDataToPeer: function(dataToSend) {
            console.log(typeof dataToSend);
            sendData(dataToSend);
        },
        callPeer: function (userToCall) {
            if (!me) {
                console.log("Warning! no peerjs connection")
            }

            isInCallCurrently = true;

            $ionicLoading.show({template: 'Dialing, hold on...' })

            getLocalStream(function (stream) {

                currentCallStream = me.call(userToCall.number, stream, { metadata: me.id});

                currentCallStream.on('error', function (err) {
                    $ionicLoading.hide();
                    isInCallCurrently = false;
                    alert(err);
                });

                currentCallStream.on('stream', function(stream) {
                    console.log('going to stream from call')
                    $ionicLoading.hide();
                    setRemoteStreamSrc(stream);

                    $timeout(function() {
                         $state.go('call', {user: userToCall});
                     }, 500)

                });

                currentCallStream.on('close', function() {
                    endCallAndGoBack();
                });
            });

            var dataConn = me.connect(userToCall.number, {reliable: true, serialization: "none"});

            if (checkIfDataConnectionIsSet(dataConn) === false) {
                setDataConnection(dataConn);
            }
        },
        endCurrentCall: function() {
            console.log('ending current call')
            isInCallCurrently = false;
            endCurrentCall();
        },
        disconnectFromPeerJS: function() {
            disconnectFromPeerJS();
        }
    }
});

<|endoftext|>


<commit_before>import moment from 'moment';
import uuidv4 from 'uuid/v4';
import { get } from 'lodash';
import models, { Op, sequelize } from '../../models';
import * as libpayments from '../../lib/payments';
import * as libtransactions from '../../lib/transactions';
import { formatCurrency } from '../../lib/utils';

/**
 * Virtual Card Payment method - This payment Method works basically as an alias
 * to other Payment method(field "SourcePaymentMethodId") that will create transactions
 * and then the payment methods of those transactions will be replaced by
 * the virtual card payment method that first processed the order.
 */

/** Get the balance of a virtual card card
 * @param {models.PaymentMethod} paymentMethod is the instance of the
 *  virtual card payment method.
 * @return {Object} with amount & currency from the payment method.
 */
async function getBalance(paymentMethod) {
  if (!libpayments.isProvider('opencollective.virtualcard', paymentMethod)) {
    throw new Error(
      `Expected opencollective.virtualcard but got ${paymentMethod.service}.${
        paymentMethod.type
      }`,
    );
  }
  let query = {
    PaymentMethodId: paymentMethod.id,
    currency: paymentMethod.currency,
    type: 'DEBIT',
  };
  let initialBalance = paymentMethod.initialBalance;
  if (paymentMethod.monthlyLimitPerMember) {
    // consider initial balance as monthly limit
    initialBalance = paymentMethod.monthlyLimitPerMember;
    // find first and last days of current month(first and last ms of those days)
    const date = new Date();
    const firstDay = new Date(date.getFullYear(), date.getMonth(), 1);
    const lastDay = new Date(date.getFullYear(), date.getMonth() + 1, 0);
    lastDay.setHours(23,59,59,999);
    // update query to filter result through the dates
    query = { ...query, createdAt: { [Op.between]: [firstDay, lastDay] } };
  }
  /* Result will be negative (We're looking for DEBIT transactions) */
  const spent = await libtransactions.sum(query);
  const balance = {
    amount: initialBalance + spent,
    currency: paymentMethod.currency,
  };
  return balance;
}

/** Process a virtual card order
 *
 * @param {models.Order} order The order instance to be processed.
 * @return {models.Transaction} the double entry generated transactions.
 */
async function processOrder(order) {
  const paymentMethod = await models.PaymentMethod.findById(
    order.paymentMethod.id,
  );
  // check if payment Method has expired
  if (
    !paymentMethod.expiryDate ||
    moment(paymentMethod.expiryDate) < moment()
  ) {
    throw new Error('Payment method has already expired');
  }

  // Checking if balance is ok or will still be after completing the order
  const balance = await getBalance(paymentMethod);
  if (!balance || balance.amount <= 0) {
    throw new Error('This payment method has no balance to complete this order');
  }
  if (balance.amount - order.totalAmount < 0) {
    throw new Error(
      `Order amount exceeds balance(${balance.amount} ${
        paymentMethod.currency
      })`,
    );
  }

  // Making sure the SourcePaymentMethodId is Set(requirement for virtual cards)
  if (!get(paymentMethod, 'SourcePaymentMethodId')) {
    throw new Error(
      'Virtual Card payment method must have a value a "SourcePaymentMethodId" defined',
    );
  }
  // finding Source Payment method and update order payment method properties
  const sourcePaymentMethod = await models.PaymentMethod.findById(
    paymentMethod.SourcePaymentMethodId,
  );
  order.PaymentMethodId = sourcePaymentMethod.id;
  order.paymentMethod = sourcePaymentMethod;
  // finding the payment provider lib to execute the order
  const sourcePaymentMethodProvider = libpayments.findPaymentMethodProvider(
    sourcePaymentMethod,
  );

  // gets the Credit transaction generated
  let creditTransaction = await sourcePaymentMethodProvider.processOrder(order);
  // gets the Debit transaction generated through the TransactionGroup field.
  const updatedTransactions = await models.Transaction.update(
    { PaymentMethodId: paymentMethod.id },
    {
      where: { TransactionGroup: creditTransaction.TransactionGroup },
      returning: true,
    },
  );
  // updating creditTransaction with latest data
  creditTransaction = updatedTransactions[1].filter(
    t => t.type === 'CREDIT',
  )[0];
  return creditTransaction;
}

/** Create Virtual payment method for a collective(organization or user)
 *
 * @param {Object} args contains the parameters to create the new
 *  payment method.
 * @param {Number} args.CollectiveId The ID of the organization creating the virtual card.
 * @param {String} args.currency The currency of the card to be created.
 * @param {Number} [args.amount] The total amount that will be
 *  credited to the newly created payment method.
 * @param {Number} [args.monthlyLimitPerMember] Limit for the value of
 *  the card that can be used per month in cents.
 * @param {String} [args.description] The description of the new payment
 *  method.
 * @param {Number} [args.PaymentMethodId] The ID of the Source Payment method the
 *                 organization wants to use
 * @param {Date} [args.expiryDate] The expiry date of the payment method
 * @param {[limitedToTags]} [args.limitedToTags] Limit this payment method to donate to collectives having those tags
 * @param {[limitedToCollectiveIds]} [args.limitedToCollectiveIds] Limit this payment method to those collective ids
 * @param {[limitedToHostCollectiveIds]} [args.limitedToHostCollectiveIds] Limit this payment method to collectives hosted by those collective ids
 * @returns {models.PaymentMethod + code} return the virtual card payment method with
            an extra property "code" that is basically the last 8 digits of the UUID
 */
async function create(args, remoteUser) {
  const collective = await models.Collective.findById(args.CollectiveId);
  let SourcePaymentMethodId = args.PaymentMethodId;
  let sourcePaymentMethod;
  if (!args.PaymentMethodId) {
    sourcePaymentMethod = await collective.getPaymentMethod(
      {
        service: 'stripe',
        type: 'creditcard',
      },
      false,
    );
    if (!sourcePaymentMethod) {
      throw Error(
        `Collective id ${
          collective.id
        } needs to have a credit card to create virtual cards.`,
      );
    }
    SourcePaymentMethodId = sourcePaymentMethod.id;
  }
  const expiryDate = args.expiryDate
    ? moment(args.expiryDate).format()
    : moment()
        .add(3, 'months')
        .format();
  // If monthlyLimitPerMember is defined, we ignore the amount field and
  // consider monthlyLimitPerMember times the months from now until the expiry date
  let monthlyLimitPerMember;
  let amount = args.amount;
  let description = `${formatCurrency(amount, args.currency)} card from ${collective.name}`;
  if (args.monthlyLimitPerMember) {
    monthlyLimitPerMember = args.monthlyLimitPerMember;
    amount = null;
    description = `${formatCurrency(args. monthlyLimitPerMember, args.currency)} monthly card from ${collective.name}`;
  }

  // creates a new Virtual card Payment method
  const paymentMethod = await models.PaymentMethod.create({
    CreatedByUserId: remoteUser && remoteUser.id,
    name: description,
    description: args.description || description,
    initialBalance: amount,
    monthlyLimitPerMember: monthlyLimitPerMember,
    currency: args.currency,
    CollectiveId: args.CollectiveId,
    expiryDate: expiryDate,
    limitedToTags: args.limitedToTags,
    limitedToCollectiveIds: args.limitedToCollectiveIds,
    limitedToHostCollectiveIds: args.limitedToHostCollectiveIds,
    uuid: uuidv4(),
    service: 'opencollective',
    type: 'virtualcard',
    SourcePaymentMethodId: SourcePaymentMethodId,
    createdAt: new Date(),
    updatedAt: new Date(),
  });
  return paymentMethod;
}

/** Claim the Virtual Card Payment Method By an (existing or not) user
 * @param {Object} args contains the parameters
 * @param {String} args.code The 8 last digits of the UUID
 * @param {email} args.user.email The email of the user claiming the virtual card
 * @returns {models.PaymentMethod} return the virtual card payment method.
 */
async function claim(args, remoteUser) {
  // validate code
  const virtualCardPaymentMethod = await models.PaymentMethod.findOne({
    where: sequelize.and(
      sequelize.where(sequelize.cast(sequelize.col('uuid'), 'text'), {
        [Op.like]: `${args.code}%`,
      }),
      { service: 'opencollective' },
      { type: 'virtualcard' },
    ),
  });
  if (!virtualCardPaymentMethod) {
    throw Error(`Code "${args.code}" invalid: No virtual card Found`);
  }
  const sourcePaymentMethod = await models.PaymentMethod.findById(
    virtualCardPaymentMethod.SourcePaymentMethodId,
  );
  // if the virtual card PM Collective Id is different than the Source PM Collective Id
  // it means this virtual card was already claimend
  if (!sourcePaymentMethod || sourcePaymentMethod.CollectiveId !== virtualCardPaymentMethod.CollectiveId) {
    throw Error('Virtual card already claimed.');
  }
  // find or creating a user with its collective
  const user = remoteUser || await models.User.findOrCreateByEmail(get(args, 'user.email'), args.user);
  if (!user) {
    throw Error(`Please provide user details or make this request as a logged in user.`);
  }
  // updating virtual card with collective Id of the user
  await virtualCardPaymentMethod.update({
    CollectiveId: user.CollectiveId,
    confirmedAt: new Date(),
  });
  virtualCardPaymentMethod.sourcePaymentMethod = sourcePaymentMethod;
  return virtualCardPaymentMethod;
}

/* Expected API of a Payment Method Type */
export default {
  features: {
    recurring: true,
    waitToCharge: false,
  },
  getBalance,
  processOrder,
  create,
  claim,
};
<commit_msg>fix(paymentProviders/virtualcard): undo modification of original order after processing source payment method order<commit_after>import moment from 'moment';
import uuidv4 from 'uuid/v4';
import { get } from 'lodash';
import models, { Op, sequelize } from '../../models';
import * as libpayments from '../../lib/payments';
import * as libtransactions from '../../lib/transactions';
import { formatCurrency } from '../../lib/utils';

/**
 * Virtual Card Payment method - This payment Method works basically as an alias
 * to other Payment method(field "SourcePaymentMethodId") that will create transactions
 * and then the payment methods of those transactions will be replaced by
 * the virtual card payment method that first processed the order.
 */

/** Get the balance of a virtual card card
 * @param {models.PaymentMethod} paymentMethod is the instance of the
 *  virtual card payment method.
 * @return {Object} with amount & currency from the payment method.
 */
async function getBalance(paymentMethod) {
  if (!libpayments.isProvider('opencollective.virtualcard', paymentMethod)) {
    throw new Error(
      `Expected opencollective.virtualcard but got ${paymentMethod.service}.${
        paymentMethod.type
      }`,
    );
  }
  let query = {
    PaymentMethodId: paymentMethod.id,
    currency: paymentMethod.currency,
    type: 'DEBIT',
  };
  let initialBalance = paymentMethod.initialBalance;
  if (paymentMethod.monthlyLimitPerMember) {
    // consider initial balance as monthly limit
    initialBalance = paymentMethod.monthlyLimitPerMember;
    // find first and last days of current month(first and last ms of those days)
    const date = new Date();
    const firstDay = new Date(date.getFullYear(), date.getMonth(), 1);
    const lastDay = new Date(date.getFullYear(), date.getMonth() + 1, 0);
    lastDay.setHours(23,59,59,999);
    // update query to filter result through the dates
    query = { ...query, createdAt: { [Op.between]: [firstDay, lastDay] } };
  }
  /* Result will be negative (We're looking for DEBIT transactions) */
  const spent = await libtransactions.sum(query);
  const balance = {
    amount: initialBalance + spent,
    currency: paymentMethod.currency,
  };
  return balance;
}

/** Process a virtual card order
 *
 * @param {models.Order} order The order instance to be processed.
 * @return {models.Transaction} the double entry generated transactions.
 */
async function processOrder(order) {
  const paymentMethod = await models.PaymentMethod.findById(
    order.paymentMethod.id,
  );
  // check if payment Method has expired
  if (
    !paymentMethod.expiryDate ||
    moment(paymentMethod.expiryDate) < moment()
  ) {
    throw new Error('Payment method has already expired');
  }

  // Checking if balance is ok or will still be after completing the order
  const balance = await getBalance(paymentMethod);
  if (!balance || balance.amount <= 0) {
    throw new Error('This payment method has no balance to complete this order');
  }
  if (balance.amount - order.totalAmount < 0) {
    throw new Error(
      `Order amount exceeds balance(${balance.amount} ${
        paymentMethod.currency
      })`,
    );
  }

  // Making sure the SourcePaymentMethodId is Set(requirement for virtual cards)
  if (!get(paymentMethod, 'SourcePaymentMethodId')) {
    throw new Error(
      'Virtual Card payment method must have a value a "SourcePaymentMethodId" defined',
    );
  }
  // finding Source Payment method and update order payment method properties
  const sourcePaymentMethod = await models.PaymentMethod.findById(
    paymentMethod.SourcePaymentMethodId,
  );
  // modifying original order to then process the order of the source payment method
  order.PaymentMethodId = sourcePaymentMethod.id;
  order.paymentMethod = sourcePaymentMethod;
  // finding the payment provider lib to execute the order
  const sourcePaymentMethodProvider = libpayments.findPaymentMethodProvider(
    sourcePaymentMethod,
  );

  // gets the Credit transaction generated
  let creditTransaction = await sourcePaymentMethodProvider.processOrder(order);
  // undo modification of original order after processing the source payment method order
  order.PaymentMethodId = paymentMethod.id;
  order.paymentMethod = paymentMethod;
  // gets the Debit transaction generated through the TransactionGroup field.
  const updatedTransactions = await models.Transaction.update(
    { PaymentMethodId: paymentMethod.id },
    {
      where: { TransactionGroup: creditTransaction.TransactionGroup },
      returning: true,
    },
  );
  // updating creditTransaction with latest data
  creditTransaction = updatedTransactions[1].filter(
    t => t.type === 'CREDIT',
  )[0];
  return creditTransaction;
}

/** Create Virtual payment method for a collective(organization or user)
 *
 * @param {Object} args contains the parameters to create the new
 *  payment method.
 * @param {Number} args.CollectiveId The ID of the organization creating the virtual card.
 * @param {String} args.currency The currency of the card to be created.
 * @param {Number} [args.amount] The total amount that will be
 *  credited to the newly created payment method.
 * @param {Number} [args.monthlyLimitPerMember] Limit for the value of
 *  the card that can be used per month in cents.
 * @param {String} [args.description] The description of the new payment
 *  method.
 * @param {Number} [args.PaymentMethodId] The ID of the Source Payment method the
 *                 organization wants to use
 * @param {Date} [args.expiryDate] The expiry date of the payment method
 * @param {[limitedToTags]} [args.limitedToTags] Limit this payment method to donate to collectives having those tags
 * @param {[limitedToCollectiveIds]} [args.limitedToCollectiveIds] Limit this payment method to those collective ids
 * @param {[limitedToHostCollectiveIds]} [args.limitedToHostCollectiveIds] Limit this payment method to collectives hosted by those collective ids
 * @returns {models.PaymentMethod + code} return the virtual card payment method with
            an extra property "code" that is basically the last 8 digits of the UUID
 */
async function create(args, remoteUser) {
  const collective = await models.Collective.findById(args.CollectiveId);
  let SourcePaymentMethodId = args.PaymentMethodId;
  let sourcePaymentMethod;
  if (!args.PaymentMethodId) {
    sourcePaymentMethod = await collective.getPaymentMethod(
      {
        service: 'stripe',
        type: 'creditcard',
      },
      false,
    );
    if (!sourcePaymentMethod) {
      throw Error(
        `Collective id ${
          collective.id
        } needs to have a credit card to create virtual cards.`,
      );
    }
    SourcePaymentMethodId = sourcePaymentMethod.id;
  }
  const expiryDate = args.expiryDate
    ? moment(args.expiryDate).format()
    : moment()
        .add(3, 'months')
        .format();
  // If monthlyLimitPerMember is defined, we ignore the amount field and
  // consider monthlyLimitPerMember times the months from now until the expiry date
  let monthlyLimitPerMember;
  let amount = args.amount;
  let description = `${formatCurrency(amount, args.currency)} card from ${collective.name}`;
  if (args.monthlyLimitPerMember) {
    monthlyLimitPerMember = args.monthlyLimitPerMember;
    amount = null;
    description = `${formatCurrency(args. monthlyLimitPerMember, args.currency)} monthly card from ${collective.name}`;
  }

  // creates a new Virtual card Payment method
  const paymentMethod = await models.PaymentMethod.create({
    CreatedByUserId: remoteUser && remoteUser.id,
    name: description,
    description: args.description || description,
    initialBalance: amount,
    monthlyLimitPerMember: monthlyLimitPerMember,
    currency: args.currency,
    CollectiveId: args.CollectiveId,
    expiryDate: expiryDate,
    limitedToTags: args.limitedToTags,
    limitedToCollectiveIds: args.limitedToCollectiveIds,
    limitedToHostCollectiveIds: args.limitedToHostCollectiveIds,
    uuid: uuidv4(),
    service: 'opencollective',
    type: 'virtualcard',
    SourcePaymentMethodId: SourcePaymentMethodId,
    createdAt: new Date(),
    updatedAt: new Date(),
  });
  return paymentMethod;
}

/** Claim the Virtual Card Payment Method By an (existing or not) user
 * @param {Object} args contains the parameters
 * @param {String} args.code The 8 last digits of the UUID
 * @param {email} args.user.email The email of the user claiming the virtual card
 * @returns {models.PaymentMethod} return the virtual card payment method.
 */
async function claim(args, remoteUser) {
  // validate code
  const virtualCardPaymentMethod = await models.PaymentMethod.findOne({
    where: sequelize.and(
      sequelize.where(sequelize.cast(sequelize.col('uuid'), 'text'), {
        [Op.like]: `${args.code}%`,
      }),
      { service: 'opencollective' },
      { type: 'virtualcard' },
    ),
  });
  if (!virtualCardPaymentMethod) {
    throw Error(`Code "${args.code}" invalid: No virtual card Found`);
  }
  const sourcePaymentMethod = await models.PaymentMethod.findById(
    virtualCardPaymentMethod.SourcePaymentMethodId,
  );
  // if the virtual card PM Collective Id is different than the Source PM Collective Id
  // it means this virtual card was already claimend
  if (!sourcePaymentMethod || sourcePaymentMethod.CollectiveId !== virtualCardPaymentMethod.CollectiveId) {
    throw Error('Virtual card already claimed.');
  }
  // find or creating a user with its collective
  const user = remoteUser || await models.User.findOrCreateByEmail(get(args, 'user.email'), args.user);
  if (!user) {
    throw Error(`Please provide user details or make this request as a logged in user.`);
  }
  // updating virtual card with collective Id of the user
  await virtualCardPaymentMethod.update({
    CollectiveId: user.CollectiveId,
    confirmedAt: new Date(),
  });
  virtualCardPaymentMethod.sourcePaymentMethod = sourcePaymentMethod;
  return virtualCardPaymentMethod;
}

/* Expected API of a Payment Method Type */
export default {
  features: {
    recurring: true,
    waitToCharge: false,
  },
  getBalance,
  processOrder,
  create,
  claim,
};
<|endoftext|>


<commit_before>(function($) {

    $.fn.domFilterText = function (options) {
        let opts = $.extend(true, {}, $.fn.domFilterText.defaults, options);

        let cacheCss = (targets) => {
            targets.each(i => {
                $(this).data('display', $(this).css('display'));
            });

            if (targets.children().length) {
                cacheCss(targets.children());
            }
        };

        cacheCss(opts.filterTarget);

        return this.each(i => {
            if (this.nodeName.toLowerCase() !== 'input' || this.type.toLowerCase() !== 'text') {
                console.warn(`(this.id ? this.id : 'Node') type is invalid, use <input type='text'> instead.`);
                return false;
            }

            $(this).on('keyup', (e) => {
                let filterVal = $(e.target).val();

                let filter = (targets) => {
                    targets.each( i => {
                        $(this).css('display', $(this).data('display'));
                    });

                    let targetRemove = targets.filter(() => {
                        let comparator = '';

                        if (opts.filterProperty === 'html') {
                            comparator = $(this).html();
                        } else {
                            comparator = $(this).filterProperty;
                        }

                        if (opts.caseSensitive) {
                            return comparator.indexOf(filterVal) < 0;
                        } else {
                            return comparator.toLowerCase().indexOf(filterVal.toLowerCase()) < 0;
                        }
                    });

                    targetRemove.not(e.target).css('display', 'none');

                    if (targets.children().length) {
                        filter(targets.children());
                    }
                };

                filter(opts.filterTarget);
            });
        });
    };

    $.fn.domFilterText.defaults = {
        filterTarget: $(document.body),
        filterProperty: 'html',
        caseSensitive: false
    };

}(jQuery));<commit_msg>add babel version<commit_after>(function($) {

    $.fn.domFilterText = function (options) {
        let self = this;
        let opts = $.extend(true, {}, $.fn.domFilterText.defaults, options);

        let cacheCss = (targets) => {
            targets.each((i, el) => {
                $(el).data('display', $(el).css('display'));
            });

            if (targets.children().length) {
                cacheCss(targets.children());
            }
        };

        cacheCss(opts.filterTarget);

        return self.each((i, el) => {
            if (el.nodeName.toLowerCase() !== 'input' || el.type.toLowerCase() !== 'text') {
                console.warn(`(el.id ? el.id : 'Node') type is invalid, use <input type='text'> instead.`);
                return false;
            }

            $(self).on('keyup', (e) => {
                let filterVal = $(e.target).val();

                let filter = (targets) => {
                    targets.each((i, el) => {
                        $(el).css('display', $(el).data('display'));
                    });

                    let targetRemove = targets.filter((i, el) => {
                        let comparator = '';

                        if (opts.filterProperty === 'html') {
                            comparator = $(el).html();
                        } else {
                            comparator = $(el).filterProperty;
                        }

                        if (opts.caseSensitive) {
                            return comparator.indexOf(filterVal) < 0;
                        } else {
                            return comparator.toLowerCase().indexOf(filterVal.toLowerCase()) < 0;
                        }
                    });

                    targetRemove.not(e.target).css('display', 'none');

                    if (targets.children().length) {
                        filter(targets.children());
                    }
                };

                filter(opts.filterTarget);
            });
        });
    };

    $.fn.domFilterText.defaults = {
        filterTarget: $(document.body),
        filterProperty: 'html',
        caseSensitive: false
    };

}(jQuery));<|endoftext|>


<commit_before>// Copyright 2002-2013, University of Colorado Boulder

/**
 * Button for a single screen in the navigation bar, shows the text and the navigation bar icon.
 *
 * @author Sam Reid
 * @author Jonathan Olson <jonathan.olson@colorado.edu>
 */
define( function( require ) {
  'use strict';

  var Node = require( 'SCENERY/nodes/Node' );
  var VBox = require( 'SCENERY/nodes/VBox' );
  var Rectangle = require( 'SCENERY/nodes/Rectangle' );
  var Text = require( 'SCENERY/nodes/Text' );
  var inherit = require( 'PHET_CORE/inherit' );
  var HighlightNode = require( 'JOIST/HighlightNode' );
  var PushButtonModel = require( 'SUN/buttons/PushButtonModel' );
  var ButtonListener = require( 'SUN/buttons/ButtonListener' );
  var Property = require( 'AXON/Property' );
  var DerivedProperty = require( 'AXON/DerivedProperty' );
  var PhetFont = require( 'SCENERY_PHET/PhetFont' );

  /**
   * Create a nav bar.  Layout assumes all of the screen widths are the same.
   * @param {Property.<string>} navigationBarFillProperty - the color of the navbar, as a string.
   * @param {Property.<number>} screenIndexProperty
   * @param {Sim} sim
   * @param {Array.<Screen>} screens - all of the available sim content screens (excluding the home screen)
   * @param {Screen} screen
   * @param {number} navBarHeight
   * @param {number} minWidth
   * @param {Object} [options]
   * @constructor
   */
  function NavigationBarScreenButton( navigationBarFillProperty, screenIndexProperty, screens, screen, navBarHeight, minWidth, options ) {
    options = _.extend( {
      tandem: null
    }, options );
    Node.call( this, {
      cursor: 'pointer',
      focusable: true,
      textDescription: screen.name + ' Screen: Button'
    } );

    var icon = new Node( {
      children: [ screen.navigationBarIcon ],
      scale: ( 0.625 * navBarHeight ) / screen.navigationBarIcon.height
    } );

    // Is this button's screen selected?
    var selectedProperty = new DerivedProperty( [ screenIndexProperty ], function( screenIndex ) {
      return screenIndex === screens.indexOf( screen );
    } );
    var buttonModel = new PushButtonModel( {
      listener: function() {
        screenIndexProperty.value = screens.indexOf( screen );
      }
    } );
    this.addInputListener( new ButtonListener( buttonModel ) );

    // Buttons are created once with the wrong size then again with the right size.  Only register the final buttons.
    options.tandem && options.tandem.addInstance( this );

    var text = new Text( screen.name, { font: new PhetFont( 10 ) } );

    var box = new VBox( {
      children: [ icon, text ],
      pickable: false,
      spacing: Math.max( 0, 12 - text.height ), // see https://github.com/phetsims/joist/issues/143
      usesOpacity: true // hint, since we change its opacity
    } );

    //add an overlay so that the icons can be placed next to each other with an HBox, also sets the touchArea/mouseArea
    var overlay = new Rectangle( 0, 0, minWidth, box.height );
    overlay.centerX = box.centerX;
    overlay.y = box.y;

    // Make things brighter when against a dark background
    var brightenHighlight = new HighlightNode( overlay.width + 4, overlay.height, {
      centerX: box.centerX,
      whiteHighlight: true,
      pickable: false
    } );

    // Make things darker when against a light background
    var darkenHighlight = new HighlightNode( overlay.width + 4, overlay.height, {
      centerX: box.centerX,
      whiteHighlight: false,
      pickable: false
    } );

    this.addChild( box );
    this.addChild( brightenHighlight );
    this.addChild( darkenHighlight );
    this.addChild( overlay );

    Property.multilink(
      [ selectedProperty, buttonModel.downProperty, buttonModel.overProperty, navigationBarFillProperty ],
      function update( selected, down, over, navigationBarFill ) {

        var useDarkenHighlights = ( navigationBarFill !== 'black' );

        // Color match yellow with the PhET Logo
        var selectedTextColor = useDarkenHighlights ? 'black' : '#f2e916';
        var unselectedTextColor = useDarkenHighlights ? 'gray' : 'white';

        text.fill = selected ? selectedTextColor : unselectedTextColor;
        box.opacity = selected ? 1.0 : ( down ? 0.65 : 0.5 );
        brightenHighlight.visible = !useDarkenHighlights && ( over || down );
        darkenHighlight.visible = useDarkenHighlights && ( over || down );
      } );
  }

  return inherit( Node, NavigationBarScreenButton );
} );
<commit_msg>add whitespace<commit_after>// Copyright 2002-2013, University of Colorado Boulder

/**
 * Button for a single screen in the navigation bar, shows the text and the navigation bar icon.
 *
 * @author Sam Reid
 * @author Jonathan Olson <jonathan.olson@colorado.edu>
 */
define( function( require ) {
  'use strict';

  var Node = require( 'SCENERY/nodes/Node' );
  var VBox = require( 'SCENERY/nodes/VBox' );
  var Rectangle = require( 'SCENERY/nodes/Rectangle' );
  var Text = require( 'SCENERY/nodes/Text' );
  var inherit = require( 'PHET_CORE/inherit' );
  var HighlightNode = require( 'JOIST/HighlightNode' );
  var PushButtonModel = require( 'SUN/buttons/PushButtonModel' );
  var ButtonListener = require( 'SUN/buttons/ButtonListener' );
  var Property = require( 'AXON/Property' );
  var DerivedProperty = require( 'AXON/DerivedProperty' );
  var PhetFont = require( 'SCENERY_PHET/PhetFont' );

  /**
   * Create a nav bar.  Layout assumes all of the screen widths are the same.
   * @param {Property.<string>} navigationBarFillProperty - the color of the navbar, as a string.
   * @param {Property.<number>} screenIndexProperty
   * @param {Sim} sim
   * @param {Array.<Screen>} screens - all of the available sim content screens (excluding the home screen)
   * @param {Screen} screen
   * @param {number} navBarHeight
   * @param {number} minWidth
   * @param {Object} [options]
   * @constructor
   */
  function NavigationBarScreenButton( navigationBarFillProperty, screenIndexProperty, screens, screen, navBarHeight, minWidth, options ) {

    options = _.extend( {
      tandem: null
    }, options );

    Node.call( this, {
      cursor: 'pointer',
      focusable: true,
      textDescription: screen.name + ' Screen: Button'
    } );

    var icon = new Node( {
      children: [ screen.navigationBarIcon ],
      scale: ( 0.625 * navBarHeight ) / screen.navigationBarIcon.height
    } );

    // Is this button's screen selected?
    var selectedProperty = new DerivedProperty( [ screenIndexProperty ], function( screenIndex ) {
      return screenIndex === screens.indexOf( screen );
    } );
    var buttonModel = new PushButtonModel( {
      listener: function() {
        screenIndexProperty.value = screens.indexOf( screen );
      }
    } );
    this.addInputListener( new ButtonListener( buttonModel ) );

    // Buttons are created once with the wrong size then again with the right size.  Only register the final buttons.
    options.tandem && options.tandem.addInstance( this );

    var text = new Text( screen.name, { font: new PhetFont( 10 ) } );

    var box = new VBox( {
      children: [ icon, text ],
      pickable: false,
      spacing: Math.max( 0, 12 - text.height ), // see https://github.com/phetsims/joist/issues/143
      usesOpacity: true // hint, since we change its opacity
    } );

    //add an overlay so that the icons can be placed next to each other with an HBox, also sets the touchArea/mouseArea
    var overlay = new Rectangle( 0, 0, minWidth, box.height );
    overlay.centerX = box.centerX;
    overlay.y = box.y;

    // Make things brighter when against a dark background
    var brightenHighlight = new HighlightNode( overlay.width + 4, overlay.height, {
      centerX: box.centerX,
      whiteHighlight: true,
      pickable: false
    } );

    // Make things darker when against a light background
    var darkenHighlight = new HighlightNode( overlay.width + 4, overlay.height, {
      centerX: box.centerX,
      whiteHighlight: false,
      pickable: false
    } );

    this.addChild( box );
    this.addChild( brightenHighlight );
    this.addChild( darkenHighlight );
    this.addChild( overlay );

    Property.multilink(
      [ selectedProperty, buttonModel.downProperty, buttonModel.overProperty, navigationBarFillProperty ],
      function update( selected, down, over, navigationBarFill ) {

        var useDarkenHighlights = ( navigationBarFill !== 'black' );

        // Color match yellow with the PhET Logo
        var selectedTextColor = useDarkenHighlights ? 'black' : '#f2e916';
        var unselectedTextColor = useDarkenHighlights ? 'gray' : 'white';

        text.fill = selected ? selectedTextColor : unselectedTextColor;
        box.opacity = selected ? 1.0 : ( down ? 0.65 : 0.5 );
        brightenHighlight.visible = !useDarkenHighlights && ( over || down );
        darkenHighlight.visible = useDarkenHighlights && ( over || down );
      } );
  }

  return inherit( Node, NavigationBarScreenButton );
} );
<|endoftext|>


<commit_before>"use strict";
const mongoose = require('mongoose');
const Media = require('./media');
const logger = require('../logger');

let topicSchema = new mongoose.Schema({
    title: { type: String, required: true },
    position: { type: Number },
    created: { type: Date, required: true },
    modified: { type: Date, required: true },
    notes: String,
    episode: { type: mongoose.Schema.Types.ObjectId, ref:'Episode' },
    medias: [{ type: mongoose.Schema.Types.ObjectId, ref:'Media' }]
});

// when a News is removed, delete all its Incrusts
topicSchema.post('remove', function(topic) {
  Media
    .remove({topic: topic._id})
    .then(function(result) {
      logger.debug("Removed all Medias from Topic " + topic._id)
    })
    .catch(function(error) {
      logger.error(error)
    })
})

module.exports = mongoose.model('Topic', topicSchema);<commit_msg>[#] Description instead of notes<commit_after>"use strict";
const mongoose = require('mongoose');
const Media = require('./media');
const logger = require('../logger');

let topicSchema = new mongoose.Schema({
    title: { type: String, required: true },
    description: String,
    position: { type: Number },
    created: { type: Date, required: true },
    modified: { type: Date, required: true },
    episode: { type: mongoose.Schema.Types.ObjectId, ref:'Episode' },
    medias: [{ type: mongoose.Schema.Types.ObjectId, ref:'Media' }]
});

// when a News is removed, delete all its Incrusts
topicSchema.post('remove', function(topic) {
  Media
    .remove({topic: topic._id})
    .then(function(result) {
      logger.debug("Removed all Medias from Topic " + topic._id)
    })
    .catch(function(error) {
      logger.error(error)
    })
})

module.exports = mongoose.model('Topic', topicSchema);<|endoftext|>