<commit_before>var _ = require('lodash');
var assert = require('assert');
var board = require('../page/board.page');
var sprintf = require('sprintf-js').sprintf;

var LoginPage = require("../page/login.page");
var ConversationsPage = require("../page/conversations.page");
var ConversationPage = require("../page/conversation.page");

var ANIMATION_DELAY = 1000;
var debugUnless = function(condF,fail){
    if(!(condF())){
        browser.debug();
    }
    else{
        console.log(fail);
    }
};
var within = function(a,b,tolerance){
    return Math.abs(a - b) <= tolerance;
};
describe('When a teacher presents,', function() {
    var teacherT = board(teacher);
    var studentT = board(student);

    var w = 1024;
    var h = 850;
    teacher.setViewportSize({width:w,height:h});
    teacher.waitUntil(function(){
        var s = teacher.getViewportSize();
        console.log(s);
        return s.state != "pending";
    });

    it('the teacher and student should find the application', function () {
        browser.url('/board');
    });

    var teacherLoginPage = LoginPage(teacher);
    var studentLoginPage = LoginPage(student);
    var teacherName = 'test.teacher.' + Math.floor(Math.random() * 10000);
    var studentName = 'test.student.' + Math.floor(Math.random() * 10000);

    var teacherConversationsPage = ConversationsPage(teacher);
    var studentConversationsPage = ConversationsPage(student);

    it('the teacher should successfully login', function () {
        teacherLoginPage.username.setValue(teacherName);
        teacherLoginPage.submit();
        assert(teacherConversationsPage.waitForSearchBox());
    });
    it('the student should successfully login', function () {
        studentLoginPage.username.setValue(studentName);
        studentLoginPage.submit();
        assert(studentConversationsPage.waitForSearchBox());
    });
    it("the teacher should be able to create and join a conversation", function() {
        assert(teacherConversationsPage.waitForCreateButton());
        var previousConversations = teacherConversationsPage.getConversations();
        teacher.click("#createConversationButton");
        var newConversations = teacherConversationsPage.getNewConversations(previousConversations);
        assert.ok(newConversations.length > 0,"expected there to be at least 1 new conversation");
        teacher.click(".newConversationTag");
        teacher.waitForExist("#board");
        teacher.pause(ANIMATION_DELAY);
    });
    it("the student should find and join the conversation",function(){
        student.setValue("#conversationSearchBox > input",teacherName);
        student.click("#searchButton");
        student.pause(1000);
        student.click(".newConversationTag");
        student.waitForExist("#board");
    });
    it("both should see a blank board", function(){
        assert.equal(_.keys(teacherT.texts).length,0);
        assert.equal(_.keys(studentT.texts).length,0);
    });
    it("the teacher should insert a paragraph",function(){
        teacherT.textMode.click();
        teacherT.keyboard(50,50,"This is a paragraph of text which is being typed programatically.  It runs over multiple lines.");
        assert.equal(_.keys(teacherT.texts).length,1);
        assert.equal(teacherT.textStanzas[_.keys(teacherT.texts)[0]].words.length,[
            "Consistently sized run"].length);
    });
    it("the teacher should highlight a word and enlarge it",function(){
        teacherT.doubleClickWorld(100,100);
        teacher.waitUntil(function(){/*Paragraph*/
            var r = teacherT.selectedRanges[0];
            return r.start == 10 && r.end == 19;
        });

        teacher.click("#fontLarger");
        teacher.waitUntil(function(){
            return teacherT.textStanzas[_.keys(teacherT.texts)[0]].words.length ==
                ["Before","Enlarged","After"].length;
        });

        teacherT.doubleClickWorld(100,300);
        teacher.waitUntil(function(){/*Programatically*/
            var r = teacherT.selectedRanges[0];
            return r.start == 49 && r.end == 65;
        });

        teacher.click("#redText");
        teacher.waitUntil(function(){
            return teacherT.textStanzas[_.keys(teacherT.texts)[0]].words.length ==
                ["Before","Enlarged","After","Red","After"].length;
        });

        teacherT.doubleClickWorld(100,400);
        teacher.waitUntil(function(){/*multiple*/
            var r = teacherT.selectedRanges[0];
            return r.start == 80 && r.end == 88;
        });

        teacher.click("#fontLarger");
        teacher.waitUntil(function(){
            return teacherT.textStanzas[_.keys(teacherT.texts)[0]].words.length ==
                ["Before","Enlarged","After","Red","After","Enlarged","After"].length;
        });
    });
    it("the teacher should create another textbox",function(){
        teacherT.keyboard(600,500,"This is a second paragraph.  It exists to be differentiated from the first paragraph.");
        assert.equal(_.keys(teacherT.texts).length,2);
        assert.equal(_.keys(teacherT.textStanzas).length,2);
        assert.equal(teacherT.textStanzas[_.keys(teacherT.texts)[1]].words.length,[
            "Consistently sized run"].length);
    });
    it("the teacher should drag their new textbox",function(){
        var active = teacherT.textStanzas[_.keys(teacherT.texts)[1]];
        assert.equal(active.x,600);
        assert.equal(active.y,500);
        assert.equal(teacherT.interactables.manualMove.length,1);
        var handle = teacherT.interactables.manualMove[0];
        teacherT.drag(handle,{x:-500,y:-250});
        active = teacherT.textStanzas[_.keys(teacherT.texts)[1]];
        assert.equal(active.x,100);
        assert(within(active.y,250,2));
    });
    it("the teacher should rescale all the font in their new textbox",function(){
        var active = teacherT.textStanzas[_.keys(teacherT.texts)[1]];
        assert.equal(active.width,240);
        assert.equal(active.x,100);

        var handle = teacherT.interactables.resizeAspectLocked[0];
        teacherT.drag(handle,{x:200,y:0});

        active = teacherT.textStanzas[_.keys(teacherT.texts)[1]];
        assert.equal(active.x,100);
        assert(within(active.width,447,2));
        assert(within(active.words[0].size, 56,2));
    });
    it("the teacher should scroll up on swipe out",function(){
        teacherT.swipeUp();
    });
    if("the teacher should be able to reselect their box",function(){
        teacherT.clickWorld(200,300);
        assert.equal(_.keys(teacherT.selection.multiWordTexts).length,1);
        assert.equal(teacherT.interactables.resizeFree.length,1);
    });
    it("the teacher should resize their box rewrapping instead of rescaling the text",function(){
        var handle = teacherT.interactables.resizeFree[0];
        teacher.pause(ANIMATION_DELAY);
        teacherT.drag(handle,{x:200,y:0});
        var active = teacherT.textStanzas[_.keys(teacherT.texts)[1]];
        assert.equal(active.x,100);
        assert(active.words.length > 0);
        assert(within(active.words[0].size, 56,2));
	console.log("Expected active width to be about 660",active.width);
	assert(within(active.width,660,3));
    });
    it("the teacher should be able to draw ink", function(){
        teacherT.inkMode.click();

        var inkStanzasBefore = _.filter(teacherT.inkStanzas,function(inkStanza){return inkStanza.author == teacherT.username;}).length;
        teacherT.handwrite(_.map(_.range(280,630,5), function(i){
            return {x:i,y:i};
        }));
        console.log("Ink stanzas",_.keys(teacherT.inkStanzas).length);
        teacher.waitUntil(function(){
            return _.filter(teacherT.inkStanzas,function(inkStanza){return inkStanza.author == teacherT.username;}).length >= (inkStanzasBefore + 1);
        },5000,"expected new ink to appear in inkStanzas after looping through server");
        for(var i = 2; i < 5; i++){
            var len = 35;
            var root = (len + 20) * i;
            teacher.click(sprintf("#pen%sButton",i));
            var pts = _.map(_.range(0,5), function(j){
                return {
                    x: root + Math.cos(j) * len--,
                    y: root + Math.sin(j) * len--
                };
            });
            teacherT.handwrite(pts);
            console.log("Ink stanzas",_.keys(teacherT.inkStanzas).length);
        }
        var v = teacherT.viewport;
        teacher.waitUntil(function(){
            return _.keys(teacherT.inkStanzas).length == 4;
        });
    });
    it("the teacher should add an image",function(){
        assert.equal(_.keys(teacherT.imageStanzas).length,0);
        teacherT.imageMode.click();
        teacher.click("#board");
        teacher.chooseFile("#imageFileChoice","testMaterials/mapleLeaf.jpg");
        teacher.waitUntil(function(){
            return _.keys(teacherT.imageStanzas).length == 1;
        },5000);
        teacher.pause(ANIMATION_DELAY);
    });
    it("the teacher should have their new image selected when it appears",function(){
        assert.equal(_.keys(teacherT.selection.images).length,1);
    });
    it("the teacher should clear their selection by clicking on an empty spot on the canvas",function(){
        teacherT.selectMode.click();
        teacherT.clickScreen(1,1);
        var sel = teacherT.selection;
        assert.equal(_.keys(sel.inks).length,0);
        assert.equal(_.keys(sel.texts).length,0);
        assert.equal(_.keys(sel.multiWordTexts).length,0);
        assert.equal(_.keys(sel.videos).length,0);
        assert.equal(_.keys(sel.images).length,0);
    });
    it("the teacher should select all the items that are under their mouse when they click the board",function(){
        console.log(_.map(teacherT.inkStanzas,"bounds"));
        teacherT.clickWorld(285,620);
        var sel = teacherT.selection;
        assert.equal(_.keys(sel.inks).length, 1);
        assert.equal(_.keys(sel.texts).length, 0);
        assert.equal(_.keys(sel.multiWordTexts).length, 1);
        assert.equal(_.keys(sel.videos).length, 0);
        assert.equal(_.keys(sel.images).length, 1);
    });
    it("the student should see all public teacher-created elements",function(){
        student.waitUntil(function(){
            return _.keys(studentT.textStanzas).length == 2 &&
                _.keys(studentT.inkStanzas).length == 4 &&
                _.keys(studentT.imageStanzas).length == 1;
        });
    });
    it("the teacher should delete their selected elements",function(){
        teacherT.deleteSelection.click();
    });
    it("the student should see all remaining public teacher-created elements",function(){
        student.waitUntil(function(){
            return _.keys(studentT.imageStanzas).length == 0;
        });
        assert.equal(_.keys(studentT.textStanzas).length,1);
        assert.equal(_.keys(studentT.inkStanzas).length,3);
    });
    it("the teacher should have their participants view show the themes so far introduced in their conversation",function(){
        assert(teacherT.cloudData.length > 0);
    });
    it("the teacher should create a private image",function(){
        teacherT.privateMode.click();
        teacherT.imageMode.click();
        teacher.click("#board");
        teacher.chooseFile("#imageFileChoice","testMaterials/mapleLeaf.jpg");
        teacher.waitUntil(function(){
            return _.keys(teacherT.imageStanzas).length == 1;
        });
        assert.equal(_.keys(studentT.imageStanzas).length,0);
    });
    it("the student should not see private teacher-created content",function(){
        assert.equal(_.keys(studentT.imageStanzas).length,0);
    });
    it("the teacher should restore deleted content from the recycle bin",function(){
        teacherT.menuButton.click();
        teacherT.recycleBinMenu.click();
        teacher.waitUntil(function(){
            return teacherT.recycleables.length == 3;
        });
    });
});
<commit_msg>adding a safety factor for grabbing handles<commit_after>var _ = require('lodash');
var assert = require('assert');
var board = require('../page/board.page');
var sprintf = require('sprintf-js').sprintf;

var LoginPage = require("../page/login.page");
var ConversationsPage = require("../page/conversations.page");
var ConversationPage = require("../page/conversation.page");

var ANIMATION_DELAY = 1000;
var debugUnless = function(condF,fail){
    if(!(condF())){
        browser.debug();
    }
    else{
        console.log(fail);
    }
};
var within = function(a,b,tolerance){
    return Math.abs(a - b) <= tolerance;
};
describe('When a teacher presents,', function() {
    var teacherT = board(teacher);
    var studentT = board(student);

    var w = 1024;
    var h = 850;
    teacher.setViewportSize({width:w,height:h});
    teacher.waitUntil(function(){
        var s = teacher.getViewportSize();
        console.log(s);
        return s.state != "pending";
    });

    it('the teacher and student should find the application', function () {
        browser.url('/board');
    });

    var teacherLoginPage = LoginPage(teacher);
    var studentLoginPage = LoginPage(student);
    var teacherName = 'test.teacher.' + Math.floor(Math.random() * 10000);
    var studentName = 'test.student.' + Math.floor(Math.random() * 10000);

    var teacherConversationsPage = ConversationsPage(teacher);
    var studentConversationsPage = ConversationsPage(student);

    it('the teacher should successfully login', function () {
        teacherLoginPage.username.setValue(teacherName);
        teacherLoginPage.submit();
        assert(teacherConversationsPage.waitForSearchBox());
    });
    it('the student should successfully login', function () {
        studentLoginPage.username.setValue(studentName);
        studentLoginPage.submit();
        assert(studentConversationsPage.waitForSearchBox());
    });
    it("the teacher should be able to create and join a conversation", function() {
        assert(teacherConversationsPage.waitForCreateButton());
        var previousConversations = teacherConversationsPage.getConversations();
        teacher.click("#createConversationButton");
        var newConversations = teacherConversationsPage.getNewConversations(previousConversations);
        assert.ok(newConversations.length > 0,"expected there to be at least 1 new conversation");
        teacher.click(".newConversationTag");
        teacher.waitForExist("#board");
        teacher.pause(ANIMATION_DELAY);
    });
    it("the student should find and join the conversation",function(){
        student.setValue("#conversationSearchBox > input",teacherName);
        student.click("#searchButton");
        student.pause(1000);
        student.click(".newConversationTag");
        student.waitForExist("#board");
    });
    it("both should see a blank board", function(){
        assert.equal(_.keys(teacherT.texts).length,0);
        assert.equal(_.keys(studentT.texts).length,0);
    });
    it("the teacher should insert a paragraph",function(){
        teacherT.textMode.click();
        teacherT.keyboard(50,50,"This is a paragraph of text which is being typed programatically.  It runs over multiple lines.");
        assert.equal(_.keys(teacherT.texts).length,1);
        assert.equal(teacherT.textStanzas[_.keys(teacherT.texts)[0]].words.length,[
            "Consistently sized run"].length);
    });
    it("the teacher should highlight a word and enlarge it",function(){
        teacherT.doubleClickWorld(100,100);
        teacher.waitUntil(function(){/*Paragraph*/
            var r = teacherT.selectedRanges[0];
            return r.start == 10 && r.end == 19;
        });

        teacher.click("#fontLarger");
        teacher.waitUntil(function(){
            return teacherT.textStanzas[_.keys(teacherT.texts)[0]].words.length ==
                ["Before","Enlarged","After"].length;
        });

        teacherT.doubleClickWorld(100,300);
        teacher.waitUntil(function(){/*Programatically*/
            var r = teacherT.selectedRanges[0];
            return r.start == 49 && r.end == 65;
        });

        teacher.click("#redText");
        teacher.waitUntil(function(){
            return teacherT.textStanzas[_.keys(teacherT.texts)[0]].words.length ==
                ["Before","Enlarged","After","Red","After"].length;
        });

        teacherT.doubleClickWorld(100,400);
        teacher.waitUntil(function(){/*multiple*/
            var r = teacherT.selectedRanges[0];
            return r.start == 80 && r.end == 88;
        });

        teacher.click("#fontLarger");
        teacher.waitUntil(function(){
            return teacherT.textStanzas[_.keys(teacherT.texts)[0]].words.length ==
                ["Before","Enlarged","After","Red","After","Enlarged","After"].length;
        });
    });
    it("the teacher should create another textbox",function(){
        teacherT.keyboard(600,500,"This is a second paragraph.  It exists to be differentiated from the first paragraph.");
        assert.equal(_.keys(teacherT.texts).length,2);
        assert.equal(_.keys(teacherT.textStanzas).length,2);
        assert.equal(teacherT.textStanzas[_.keys(teacherT.texts)[1]].words.length,[
            "Consistently sized run"].length);
    });
    it("the teacher should drag their new textbox",function(){
        var active = teacherT.textStanzas[_.keys(teacherT.texts)[1]];
        assert.equal(active.x,600);
        assert.equal(active.y,500);
        assert.equal(teacherT.interactables.manualMove.length,1);
        var handle = teacherT.interactables.manualMove[0];
        teacherT.drag(handle,{x:-500,y:-250});
        active = teacherT.textStanzas[_.keys(teacherT.texts)[1]];
        assert.equal(active.x,100);
        assert(within(active.y,250,2));
    });
    it("the teacher should rescale all the font in their new textbox",function(){
        var active = teacherT.textStanzas[_.keys(teacherT.texts)[1]];
        assert.equal(active.width,240);
        assert.equal(active.x,100);

        var handle = teacherT.interactables.resizeAspectLocked[0];
        teacherT.drag(handle,{x:200,y:0});

        active = teacherT.textStanzas[_.keys(teacherT.texts)[1]];
        assert.equal(active.x,100);
        assert(within(active.width,447,2));
        assert(within(active.words[0].size, 56,2));
    });
    it("the teacher should scroll up on swipe out",function(){
        teacherT.swipeUp();
    });
    if("the teacher should be able to reselect their box",function(){
        teacherT.clickWorld(200,300);
        assert.equal(_.keys(teacherT.selection.multiWordTexts).length,1);
        assert.equal(teacherT.interactables.resizeFree.length,1);
    });
    it("the teacher should resize their box rewrapping instead of rescaling the text",function(){
        var handle = teacherT.interactables.resizeFree[0];
        teacher.pause(ANIMATION_DELAY);
        teacherT.drag(handle,{x:200,y:0});
        var active = teacherT.textStanzas[_.keys(teacherT.texts)[1]];
        assert.equal(active.x,100);
        assert(active.words.length > 0);
        assert(within(active.words[0].size, 56,2));
	console.log("Expected active width to be about 660",active.width);
	assert(within(active.width,660,5));
    });
    it("the teacher should be able to draw ink", function(){
        teacherT.inkMode.click();

        var inkStanzasBefore = _.filter(teacherT.inkStanzas,function(inkStanza){return inkStanza.author == teacherT.username;}).length;
        teacherT.handwrite(_.map(_.range(280,630,5), function(i){
            return {x:i,y:i};
        }));
        console.log("Ink stanzas",_.keys(teacherT.inkStanzas).length);
        teacher.waitUntil(function(){
            return _.filter(teacherT.inkStanzas,function(inkStanza){return inkStanza.author == teacherT.username;}).length >= (inkStanzasBefore + 1);
        },5000,"expected new ink to appear in inkStanzas after looping through server");
        for(var i = 2; i < 5; i++){
            var len = 35;
            var root = (len + 20) * i;
            teacher.click(sprintf("#pen%sButton",i));
            var pts = _.map(_.range(0,5), function(j){
                return {
                    x: root + Math.cos(j) * len--,
                    y: root + Math.sin(j) * len--
                };
            });
            teacherT.handwrite(pts);
            console.log("Ink stanzas",_.keys(teacherT.inkStanzas).length);
        }
        var v = teacherT.viewport;
        teacher.waitUntil(function(){
            return _.keys(teacherT.inkStanzas).length == 4;
        });
    });
    it("the teacher should add an image",function(){
        assert.equal(_.keys(teacherT.imageStanzas).length,0);
        teacherT.imageMode.click();
        teacher.click("#board");
        teacher.chooseFile("#imageFileChoice","testMaterials/mapleLeaf.jpg");
        teacher.waitUntil(function(){
            return _.keys(teacherT.imageStanzas).length == 1;
        },5000);
        teacher.pause(ANIMATION_DELAY);
    });
    it("the teacher should have their new image selected when it appears",function(){
        assert.equal(_.keys(teacherT.selection.images).length,1);
    });
    it("the teacher should clear their selection by clicking on an empty spot on the canvas",function(){
        teacherT.selectMode.click();
        teacherT.clickScreen(1,1);
        var sel = teacherT.selection;
        assert.equal(_.keys(sel.inks).length,0);
        assert.equal(_.keys(sel.texts).length,0);
        assert.equal(_.keys(sel.multiWordTexts).length,0);
        assert.equal(_.keys(sel.videos).length,0);
        assert.equal(_.keys(sel.images).length,0);
    });
    it("the teacher should select all the items that are under their mouse when they click the board",function(){
        console.log(_.map(teacherT.inkStanzas,"bounds"));
        teacherT.clickWorld(285,620);
        var sel = teacherT.selection;
        assert.equal(_.keys(sel.inks).length, 1);
        assert.equal(_.keys(sel.texts).length, 0);
        assert.equal(_.keys(sel.multiWordTexts).length, 1);
        assert.equal(_.keys(sel.videos).length, 0);
        assert.equal(_.keys(sel.images).length, 1);
    });
    it("the student should see all public teacher-created elements",function(){
        student.waitUntil(function(){
            return _.keys(studentT.textStanzas).length == 2 &&
                _.keys(studentT.inkStanzas).length == 4 &&
                _.keys(studentT.imageStanzas).length == 1;
        });
    });
    it("the teacher should delete their selected elements",function(){
        teacherT.deleteSelection.click();
    });
    it("the student should see all remaining public teacher-created elements",function(){
        student.waitUntil(function(){
            return _.keys(studentT.imageStanzas).length == 0;
        });
        assert.equal(_.keys(studentT.textStanzas).length,1);
        assert.equal(_.keys(studentT.inkStanzas).length,3);
    });
    it("the teacher should have their participants view show the themes so far introduced in their conversation",function(){
        assert(teacherT.cloudData.length > 0);
    });
    it("the teacher should create a private image",function(){
        teacherT.privateMode.click();
        teacherT.imageMode.click();
        teacher.click("#board");
        teacher.chooseFile("#imageFileChoice","testMaterials/mapleLeaf.jpg");
        teacher.waitUntil(function(){
            return _.keys(teacherT.imageStanzas).length == 1;
        });
        assert.equal(_.keys(studentT.imageStanzas).length,0);
    });
    it("the student should not see private teacher-created content",function(){
        assert.equal(_.keys(studentT.imageStanzas).length,0);
    });
    it("the teacher should restore deleted content from the recycle bin",function(){
        teacherT.menuButton.click();
        teacherT.recycleBinMenu.click();
        teacher.waitUntil(function(){
            return teacherT.recycleables.length == 3;
        });
    });
});
<|endoftext|>


<commit_before>define('events', ['components', 'consts', 'buttons', 'utils'], function(components, consts, buttons, utils) {
    'use strict';

    const
        _start = () => {
            let gameContainerClass = new components.GameContainer(),
                panelGamerClass = new components.PanelGamer(),
                panelContainer = gameContainerClass.getGameContainer();

            utils.emptyContainer('parentContainer');
            gameContainerClass.appendGameContainer(consts.DOC.getElementById('parentContainer'));
            panelGamerClass.appendPanelGamer(panelContainer);
        },
        _save = () => {
            // TODO: pending...
        },
        _load = () => {
            // TODO: pending...
        },
        _options = () => {
            utils.emptyContainer('parentContainer');
            // TODO: pending...
        },
        _exit = () => {
            // TODO: pending...
        },
        _next = () => {
            utils.emptyContainer('parentContainer');
            // TODO: pending...
        },
        _back = () => {
            utils.emptyContainer('parentContainer');
            // TODO: pending...
        };

    // on click
    buttons.START.onclick = () => {
        _start();
    };
    buttons.SAVE.onclick = () => {
        _save();
    };
    buttons.LOAD.onclick = () => {
        _load();
    };
    buttons.OPTIONS.onclick = () => {
        _options();
    };
    buttons.EXIT.onclick = () => {
        _exit();
    };
    buttons.NEXT.onclick = () => {
        _next();
    };
    buttons.BACK.onclick = () => {
        _back();
    };

    return {
        start: _start,
        save: _save,
        load: _load,
        options: _options,
        exit: _exit,
        next: _next,
        back: _back
    };
});
<commit_msg>Append panelEnemy in DOM when start the game<commit_after>define('events', ['components', 'consts', 'buttons', 'utils'], function(components, consts, buttons, utils) {
    'use strict';

    const
        _start = () => {
            let gameContainerClass = new components.GameContainer(),
                panelGamerClass = new components.PanelGamer(),
                panelEnemyClass = new components.PanelEnemy(),
                panelContainer = gameContainerClass.getGameContainer();

            utils.emptyContainer('parentContainer');
            gameContainerClass.appendGameContainer(consts.DOC.getElementById('parentContainer'));
            panelGamerClass.appendPanelGamer(panelContainer);
            panelEnemyClass.appendPanelEnemy(panelContainer);
        },
        _save = () => {
            // TODO: pending...
        },
        _load = () => {
            // TODO: pending...
        },
        _options = () => {
            utils.emptyContainer('parentContainer');
            // TODO: pending...
        },
        _exit = () => {
            // TODO: pending...
        },
        _next = () => {
            utils.emptyContainer('parentContainer');
            // TODO: pending...
        },
        _back = () => {
            utils.emptyContainer('parentContainer');
            // TODO: pending...
        };

    // on click
    buttons.START.onclick = () => {
        _start();
    };
    buttons.SAVE.onclick = () => {
        _save();
    };
    buttons.LOAD.onclick = () => {
        _load();
    };
    buttons.OPTIONS.onclick = () => {
        _options();
    };
    buttons.EXIT.onclick = () => {
        _exit();
    };
    buttons.NEXT.onclick = () => {
        _next();
    };
    buttons.BACK.onclick = () => {
        _back();
    };

    return {
        start: _start,
        save: _save,
        load: _load,
        options: _options,
        exit: _exit,
        next: _next,
        back: _back
    };
});
<|endoftext|>


<commit_before>(function (Helper) {
    'use strict';
    var moment = require('moment');

    var database = require('./database');

    Helper.checkLoggedIn = function(uid) {
        return !(parseInt(uid, 10) == 0);
    }

    Helper.getSignInTsAndContinDays = function (uid, callback) {
        if (!Helper.checkLoggedIn()) {
            return callback(null, false);
        }

        database.getSignInTsAndContinDays(uid, function (err, data) {
            if (err) {
                return callback(err);
            }

            var cb_data = {};
            if (data.signin_timestamp) {
                var lastSignTime = moment(data.signin_timestamp);
                var now = moment();
                cb_data.canSignIn = lastSignTime.isBefore(now, 'day');
                cb_data.continDays = cb_data.canSignIn ? (now.subtract(1, 'day').isSame(lastSignTime, 'day')
                                     ? data.contin_days : 0) : data.contin_days;

            } else {
                cb_data.canSignIn = true;
                cb_data.continDays = 0;
            }

            console.log(data);
            console.log(cb_data);

            return callback(null, cb_data);
        });
    };

})(module.exports);<commit_msg>500 -> 400<commit_after>(function (Helper) {
    'use strict';
    var moment = require('moment');

    var database = require('./database');

    Helper.checkLoggedIn = function(uid) {
        return !(parseInt(uid, 10) == 0);
    }

    Helper.getSignInTsAndContinDays = function (uid, callback) {
        if (!Helper.checkLoggedIn()) {
            return callback(null, false);
        }

        database.getSignInTsAndContinDays(uid, function (err, data) {
            if (err) {
                return callback(err);
            }

            var cb_data = {};
            if (data.signin_timestamp) {
                var lastSignTime = moment(data.signin_timestamp);
                var now = moment();
                cb_data.canSignIn = lastSignTime.isBefore(now, 'day');
                cb_data.continDays = cb_data.canSignIn ? (now.subtract(1, 'day').isSame(lastSignTime, 'day')
                                     ? data.contin_days : 0) : data.contin_days;

            } else {
                cb_data.canSignIn = true;
                cb_data.continDays = 0;
            }

            return callback(null, cb_data);
        });
    };

})(module.exports);<|endoftext|>


<commit_before>import React from 'react';
import { GridTile } from 'material-ui/GridList';
import IconButton from 'material-ui/IconButton';
import StarBorder from 'material-ui/svg-icons/toggle/star-border';

class PhotoTileComponent extends React.Component {

  constructor(props) {
    super(props);
  }

  render() {
    const tile = this.props.tile;
    return (
      <GridTile
        key={tile.key}
        title={tile.title}
        subtitle={tile.author}
        actionIcon={<IconButton><StarBorder color="white" /></IconButton>}
      >

      </GridTile>
    );
  }
}

PhotoTileComponent.propTypes = {
  tile: React.PropTypes.object.isRequired,
  style: React.PropTypes.object.isRequired
};

export default PhotoTileComponent;
<commit_msg>make component stateless<commit_after>import React from 'react';
import { GridTile } from 'material-ui/GridList';
import IconButton from 'material-ui/IconButton';
import StarBorder from 'material-ui/svg-icons/toggle/star-border';

const PhotoTileComponent = (props) =>
  <GridTile
    title={props.tile.title}
    subtitle={props.tile.author}
    actionIcon={<IconButton><StarBorder color="white" /></IconButton>}
  >

  </GridTile>;

PhotoTileComponent.propTypes = {
  tile: React.PropTypes.object.isRequired,
  style: React.PropTypes.object.isRequired
};

export default PhotoTileComponent;
<|endoftext|>


<commit_before>{
  /**
   * @class InvertedIndex
   */
  class InvertedIndex {
    /**
     * Class Constructor
     * @constructor
     */
    constructor() {
      this.allIndices = {};
    }

    /**
     * Reads the data from the file being uploaded
     * @param  {File} file - Uploaded file to be read.
     * @return {void}
     */
    static readFile(file) {
      const reader = new FileReader();
      reader.onload = () => reader.result;
      reader.readAsText(file);
    }
     /**
     * Ensures all the documents in a particular file is valid
     * @param  {Object} file
     * @return {[Boolean]} isValid -True or false
     */
    validateFile(file) {
      this.file = file;
      let checkField,
        isValid = true;
      try {
        const parsedJSON = JSON.parse(JSON.stringify(this.file));
        isValid = (parsedJSON.length === 0) ? false : checkField;
        checkField = parsedJSON.forEach((key) => {
          isValid = (typeof key.title !== 'string' ||
            typeof key.text !== 'string') ? false : isValid;
        });
      } catch (error) {
        isValid = false;
      }
      return isValid;
    }
    /**
     * Strips out special characters from documents to be indexed
     * @param  {String} fileText - String from file to be tokenized
     * @return {Array} An array of unique words
     */
    tokenize(fileText) {
      this.text = fileText;
      return [...new Set(this.text
                .toLowerCase()        // Converts text to lower case
                .replace(/[^\w\s]/g, '')  // Removes any non-word character
                .split(/\s+/)       // Turns it into an array
                .sort()           // Sorts array
      )];
    }
    /**
     * Creates the index for documents
     * @param  {String} fileName
     * @param  {String} content
     * @return {Object} this.allIndices
     */
    createIndex(fileName, content) {
      const fileIndex = {};

      content.forEach((objDoc, index) => {
        for (const key in objDoc) {
          const tokens = this.tokenize(objDoc[key]);

          tokens.forEach((token) => {
            if (fileIndex[token]) {
              if (fileIndex[token].indexOf(index) === -1) { // Checks for index
                fileIndex[token].push(index);
              }
            } else {
              fileIndex[token] = [index];
            }
          });
        }
      });
      this.allIndices[fileName] = { words: fileIndex, count: content.length };
      return this.allIndices[fileName];
    }
    /**
     * Get’s indices created for particular files
     * @param  {Object} fileName -Title of input file
     * @return {Object} allIndices
     */
    getIndex(fileName) {
      return this.allIndices[fileName];
    }
    /**
     * Searches through one or more indices for words
     * @param  {String} fileName -File name
     * @param  {String} query -Input token
     * @return {Object} searchResult
     */
    searchIndex(fileName, query) {
      const queryTokens = this.tokenize(query),
        index = this.getIndex(fileName);
      if (!index) {
        return `Index with ${fileName} does not exist`;
      }

      const searchResult = {
        words: {},
        docCount: index.docCount
      };

      queryTokens.forEach((token) => {
        if (index.words[token]) {
          searchResult.words[token] = index.words[token];
        }
      });

      return Object.keys(searchResult.words).length > 0
      ? searchResult : 'No word found';
    }
  }

  /** App exported as Node package */
  module.exports = InvertedIndex;
}
<commit_msg>validateFile method modified<commit_after>{
  /**
   * @class InvertedIndex
   */
  class InvertedIndex {
    /**
     * Class Constructor
     * @constructor
     */
    constructor() {
      this.allIndices = {};
    }
    /**
     * Method that handles file upload
     * @return {Array} An array of all files uploaded
     */
    static uploadFile() {
      let uploadedFiles = [];
      if (window.File && window.FileReader && window.FileList
        && window.Blob) {
        let fileUpload = document.getElementsByClassName('file-upload')[0];
        if (this.validateFile(fileUpload)) {
          uploadedFiles.push(fileUpload);
        }
      } else {
        return 'The File APIs are not fully supported in this browser.';
      }
    }
    /**
     * Reads the data from the file being uploaded
     * @param  {File} file - Uploaded file to be read.
     * @return {void}
     */
    static readFile(file) {
      const reader = new FileReader();
      reader.onload = () => reader.result;
      reader.readAsText(file);
    }
     /**
     * Ensures all the documents in a particular file is valid
     * @param  {Object} file
     * @return {Boolean} isValid -True or false
     */
    validateFile(file) {
      this.file = file;
      let isValid = true;
      try {
        const parsedJSON = JSON.parse(JSON.stringify(this.file));
        isValid = (parsedJSON.length === 0) ? false : isValid;
        parsedJSON.forEach((key) => {
          if (typeof key.title !== 'string' || typeof key.text !== 'string') {
            isValid = false;
          }
        });
      } catch (error) {
        isValid = false;
      }
      return isValid;
    }
    /**
     * Strips out special characters from documents to be indexed
     * @param  {String} fileText - String from file to be tokenized
     * @return {Array} An array of unique words
     */
    tokenize(fileText) {
      this.text = fileText;
      return [...new Set(this.text
                .toLowerCase()        // Converts text to lower case
                .replace(/[^\w\s]/g, '')  // Removes any non-word character
                .split(/\s+/)       // Turns it into an array
                .sort()           // Sorts array
      )];
    }
    /**
     * Creates the index for documents
     * @param  {String} fileName
     * @param  {String} content
     * @return {Object} this.allIndices
     */
    createIndex(fileName, content) {
      const fileIndex = {};

      content.forEach((objDoc, index) => {
        for (const key in objDoc) {
          const tokens = this.tokenize(objDoc[key]);

          tokens.forEach((token) => {
            if (fileIndex[token]) {
              if (fileIndex[token].indexOf(index) === -1) { // Checks for index
                fileIndex[token].push(index);
              }
            } else {
              fileIndex[token] = [index];
            }
          });
        }
      });
      this.allIndices[fileName] = { words: fileIndex, count: content.length };
      return this.allIndices[fileName];
    }
    /**
     * Get’s indices created for particular files
     * @param  {Object} fileName -Title of input file
     * @return {Object} allIndices
     */
    getIndex(fileName) {
      return this.allIndices[fileName];
    }
    /**
     * Searches through one or more indices for words
     * @param  {String} fileName -File name
     * @param  {String} query -Input token
     * @return {Object} searchResult
     */
    searchIndex(fileName, query) {
      const queryTokens = this.tokenize(query),
        index = this.getIndex(fileName);
      if (!index) {
        return `Index with ${fileName} does not exist`;
      }

      const searchResult = {
        words: {},
        docCount: index.docCount
      };

      queryTokens.forEach((token) => {
        if (index.words[token]) {
          searchResult.words[token] = index.words[token];
        }
      });

      return Object.keys(searchResult.words).length > 0
      ? searchResult : 'No word found';
    }
  }

  /** App exported as Node package */
  if (typeof window === 'undefined') {
    module.exports = InvertedIndex;
  }
}
<|endoftext|>


<commit_before>'use strict';
var cl = function (val) {
    return console.log(val);
};

// Declare app level module which depends on filters, and services
var KMCModule = angular.module('KMCModule',
    ['pascalprecht.translate', 'ngRoute', 'KMC.controllers', 'KMC.filters',
        'KMC.services', 'KMC.directives', 'ngAnimate', 'LocalStorageModule', 'KMC.menu']);

KMCModule.config(['$routeProvider', '$locationProvider', '$httpProvider', '$tooltipProvider', '$translateProvider', function ($routeProvider, $locationProvider, $httpProvider, $tooltipProvider, $translateProvider) {
        $translateProvider.useStaticFilesLoader({
            prefix: 'i18n/',
            suffix: '.json'
        });
        $translateProvider.preferredLanguage('en_US');
        $translateProvider.fallbackLanguage('en_US');
        if (window.location.href.indexOf('debug') != -1) {
            $translateProvider.useMissingTranslationHandlerLog();
        }
        $translateProvider.useStorage('localStorageService');
        $tooltipProvider.options({ placement: 'right', 'appendToBody': true, 'popupDelay': 800 });
        // set event name for opening and closing the tooltips
        $tooltipProvider.setTriggers({
            'customShow': 'customShow'
        });

        $httpProvider.defaults.useXDomain = true; // cors support (TODO - check if we can remove, should be true by default in this angular version)
        delete $httpProvider.defaults.headers.common['X-Requested-With']; // IE8 cors support

        //request loading indication
        var $http, interceptor = ['$q', '$injector',
            function ($q, $injector) {
                var notificationChannel;

                function success(response) {
                    // get $http via $injector because of circular dependency problem
                    $http = $http || $injector.get('$http');
                    // don't send notification until all requests are complete
                    if ($http.pendingRequests.length < 1) {
                        // get requestNotificationChannel via $injector because of circular dependency problem
                        notificationChannel = notificationChannel || $injector.get('requestNotificationChannel');
                        // send a notification requests are complete
                        notificationChannel.requestEnded();
                    }
                    return response;
                }

                function error(response) {
                    logTime('httpRequest failed -');
                    // get $http via $injector because of circular dependency problem
                    $http = $http || $injector.get('$http');
                    // don't send notification until all requests are complete
                    if ($http.pendingRequests.length < 1) {
                        // get requestNotificationChannel via $injector because of circular dependency problem
                        notificationChannel = notificationChannel || $injector.get('requestNotificationChannel');
                        // send a notification requests are complete
                        notificationChannel.requestEnded();
                    }
                    return $q.reject(response);
                }

                return function (promise) {
                    // get requestNotificationChannel via $injector because of circular dependency problem
                    notificationChannel = notificationChannel || $injector.get('requestNotificationChannel');
                    // send a notification requests are complete
                    notificationChannel.requestStarted();
                    return promise.then(success, error);
                };
            }];
        // add the interceptor to the httpProvider
        $httpProvider.responseInterceptors.push(interceptor);
        // routing section
        $routeProvider.when('/login', {
                templateUrl: 'view/login.html',
                controller: 'LoginCtrl',
                resolve: {'apiService': ['api', 'apiService', function (api, apiService) {
                    // make sure apiService is available upon invalid KS redirect
                    return apiService;
                }]
                }
            }
        );
        $routeProvider.when('/list', {
                templateUrl: 'view/list.html',
                controller: 'PlayerListCtrl',
                resolve: {
                    'apiService': ['api', 'apiService', 'localStorageService', '$location', function (api, apiService, localStorageService, $location) {
                        // make sure we load the list only if we have valid KS
                        return ksCheck(api, apiService, localStorageService, $location).then(function () {
                            return apiService;
                        });
                    }]
                }
            }
        );
        var ksCheck = function (api, apiService, localStorageService, $location) {
            // Check if we have ks in locaclstorage
            try {
                var kmc = window.parent.kmc;
                if (kmc && kmc.vars) {
                    // got ks from KMC - save to local storage
                    if (kmc.vars.ks)
                        localStorageService.add('ks', kmc.vars.ks);
                }
            } catch (e) {
                cl('Could not located parent.kmc: ' + e);
            }
            var ks = localStorageService.get('ks');
            if (!ks) { //navigate to login
                $location.path("/login");
                return false;
            } else {
                api.then(function () {
                    apiService.setKs(ks);
                });
            }
            return api; // changed to return the promise of the API
        };

        // deep linking to plugin setup
        $routeProvider.when('/edit/:id',
            {templateUrl: 'view/edit.html',
                controller: 'PlayerEditCtrl',
                reloadOnSearch: false,
                resolve: {
                    'PlayerData': ['PlayerService', '$route', 'api', 'apiService', 'localStorageService', '$location', function (PlayerService, $route, api, apiService, localStorageService, $location) {
                        var apiLoaded = ksCheck(api, apiService, localStorageService, $location);
                        if (apiLoaded) {
                            return apiLoaded.then(function (api) {
                                return PlayerService.getPlayer($route.current.params.id);
                            });
                        }
                    }],
                    'editProperties': 'editableProperties',
                    'menuSvc': 'menuSvc'
                }
            }
        );

        // open template screen
        $routeProvider.when('/newByTemplate',
            {templateUrl: 'view/new-template.html',
                controller: 'PlayerCreateCtrl',
                resolve: {
                    'templates': ['playerTemplates', function (playerTemplates) {
                        return  playerTemplates.listSystem();
                    }],
                    'userId': function () {
                        return '1'; //  KMC would need to give us the userID ?
                    }

                }
            }
        );
        $routeProvider.when('/new',
            {templateUrl: 'view/edit.html',
                controller: 'PlayerEditCtrl',
                resolve: {
                    'api': ['api', 'apiService', 'localStorageService', '$location', function (api, apiService, localStorageService, $location) {
                        return ksCheck(api, apiService, localStorageService, $location);
                    }],
                    'PlayerData': function (api, PlayerService) {
                        return api.then(function () {
                            return PlayerService.newPlayer();
                        });
                    },
                    'menuSvc': 'menuSvc'
                }
            }
        );
        $routeProvider.when('/logout', {
            resolve: {'logout': ['localStorageService', 'apiService', '$location', function (localStorageService, apiService, $location) {
                if (localStorageService.isSupported()) {
                    localStorageService.clearAll();
                }
                apiService.unSetks();
                $location.path('/login');
            }]}

        });
        $routeProvider.otherwise({
            resolve: {'res': ['api', 'apiService', 'localStorageService', '$location', function (api, apiService, localStorageService, $location) {
                if (ksCheck(api, apiService, localStorageService, $location)) {
                    return $location.path('/list');
                }
            }]}
        });
    }]).run(function ($rootScope, $rootElement, $location, menuSvc) {
    var appLoad = new Date();
    var debug = false;

    // set the logTime function used in debug mode
    var logTime = function (eventName) {
        if ($location.search()['debug']) {
            var now = new Date();
            var diff = Math.abs(appLoad.getTime() - now.getTime());
            cl(eventName + ' ' + Math.ceil(diff / 1000) + 'sec ' + diff % 1000 + 'ms');
        }
    };
    window.logTime = logTime;
    logTime('AppJsLoad');

    // add functions to $rootScope constructor. $safeApply to prevent apply when digest is already in progress
    $rootScope.constructor.prototype.$safeApply = function (fn) {
        var phase = this.$root.$$phase;
        if (phase == '$apply' || phase == '$digest')
            this.$eval(fn);
        else
            this.$apply(fn);
    };

    // custom show for tooltips
    $rootScope.constructor.prototype.openTooltip = function ($event) {
        menuSvc.currentTooltip = $event.target;
        $($event.target).trigger('customShow');
        $event.preventDefault();
        $event.stopPropagation();
        return false;
    };

    // for css - define the body ID according to route
    $rootScope.routeName = '';
    $rootScope.$on('$routeChangeSuccess', function () {
        appLoad = new Date();
        var url = $location.url().split('/');
        if (debug) {
            $location.search({debug: true});
        }
        if (url[1].indexOf('?') != -1) {
            url[1] = url[1].substr(0, url[1].indexOf('?'));
        }
        $rootScope.routeName = url[1];
    });

    // set debug flag across route changes
    $rootScope.$on('$routeChangeStart', function () {
        if ($location.search()['debug']) {
            debug = true;
        }
        else {
            debug = false;
        }
    });
});
<commit_msg>fixed tooltip flicker upon click (when mouseover is triggeed already..)<commit_after>'use strict';
var cl = function (val) {
    return console.log(val);
};

// Declare app level module which depends on filters, and services
var KMCModule = angular.module('KMCModule',
    ['pascalprecht.translate', 'ngRoute', 'KMC.controllers', 'KMC.filters',
        'KMC.services', 'KMC.directives', 'ngAnimate', 'LocalStorageModule', 'KMC.menu']);

KMCModule.config(['$routeProvider', '$locationProvider', '$httpProvider', '$tooltipProvider', '$translateProvider', function ($routeProvider, $locationProvider, $httpProvider, $tooltipProvider, $translateProvider) {
        $translateProvider.useStaticFilesLoader({
            prefix: 'i18n/',
            suffix: '.json'
        });
        $translateProvider.preferredLanguage('en_US');
        $translateProvider.fallbackLanguage('en_US');
        if (window.location.href.indexOf('debug') != -1) {
            $translateProvider.useMissingTranslationHandlerLog();
        }
        $translateProvider.useStorage('localStorageService');
        $tooltipProvider.options({ placement: 'right', 'appendToBody': true, 'popupDelay': 800 });
        // set event name for opening and closing the tooltips
        $tooltipProvider.setTriggers({
            'customShow': 'customShow'
        });

        $httpProvider.defaults.useXDomain = true; // cors support (TODO - check if we can remove, should be true by default in this angular version)
        delete $httpProvider.defaults.headers.common['X-Requested-With']; // IE8 cors support

        //request loading indication
        var $http, interceptor = ['$q', '$injector',
            function ($q, $injector) {
                var notificationChannel;

                function success(response) {
                    // get $http via $injector because of circular dependency problem
                    $http = $http || $injector.get('$http');
                    // don't send notification until all requests are complete
                    if ($http.pendingRequests.length < 1) {
                        // get requestNotificationChannel via $injector because of circular dependency problem
                        notificationChannel = notificationChannel || $injector.get('requestNotificationChannel');
                        // send a notification requests are complete
                        notificationChannel.requestEnded();
                    }
                    return response;
                }

                function error(response) {
                    logTime('httpRequest failed -');
                    // get $http via $injector because of circular dependency problem
                    $http = $http || $injector.get('$http');
                    // don't send notification until all requests are complete
                    if ($http.pendingRequests.length < 1) {
                        // get requestNotificationChannel via $injector because of circular dependency problem
                        notificationChannel = notificationChannel || $injector.get('requestNotificationChannel');
                        // send a notification requests are complete
                        notificationChannel.requestEnded();
                    }
                    return $q.reject(response);
                }

                return function (promise) {
                    // get requestNotificationChannel via $injector because of circular dependency problem
                    notificationChannel = notificationChannel || $injector.get('requestNotificationChannel');
                    // send a notification requests are complete
                    notificationChannel.requestStarted();
                    return promise.then(success, error);
                };
            }];
        // add the interceptor to the httpProvider
        $httpProvider.responseInterceptors.push(interceptor);
        // routing section
        $routeProvider.when('/login', {
                templateUrl: 'view/login.html',
                controller: 'LoginCtrl',
                resolve: {'apiService': ['api', 'apiService', function (api, apiService) {
                    // make sure apiService is available upon invalid KS redirect
                    return apiService;
                }]
                }
            }
        );
        $routeProvider.when('/list', {
                templateUrl: 'view/list.html',
                controller: 'PlayerListCtrl',
                resolve: {
                    'apiService': ['api', 'apiService', 'localStorageService', '$location', function (api, apiService, localStorageService, $location) {
                        // make sure we load the list only if we have valid KS
                        return ksCheck(api, apiService, localStorageService, $location).then(function () {
                            return apiService;
                        });
                    }]
                }
            }
        );
        var ksCheck = function (api, apiService, localStorageService, $location) {
            // Check if we have ks in locaclstorage
            try {
                var kmc = window.parent.kmc;
                if (kmc && kmc.vars) {
                    // got ks from KMC - save to local storage
                    if (kmc.vars.ks)
                        localStorageService.add('ks', kmc.vars.ks);
                }
            } catch (e) {
                cl('Could not located parent.kmc: ' + e);
            }
            var ks = localStorageService.get('ks');
            if (!ks) { //navigate to login
                $location.path("/login");
                return false;
            } else {
                api.then(function () {
                    apiService.setKs(ks);
                });
            }
            return api; // changed to return the promise of the API
        };

        // deep linking to plugin setup
        $routeProvider.when('/edit/:id',
            {templateUrl: 'view/edit.html',
                controller: 'PlayerEditCtrl',
                reloadOnSearch: false,
                resolve: {
                    'PlayerData': ['PlayerService', '$route', 'api', 'apiService', 'localStorageService', '$location', function (PlayerService, $route, api, apiService, localStorageService, $location) {
                        var apiLoaded = ksCheck(api, apiService, localStorageService, $location);
                        if (apiLoaded) {
                            return apiLoaded.then(function (api) {
                                return PlayerService.getPlayer($route.current.params.id);
                            });
                        }
                    }],
                    'editProperties': 'editableProperties',
                    'menuSvc': 'menuSvc'
                }
            }
        );

        // open template screen
        $routeProvider.when('/newByTemplate',
            {templateUrl: 'view/new-template.html',
                controller: 'PlayerCreateCtrl',
                resolve: {
                    'templates': ['playerTemplates', function (playerTemplates) {
                        return  playerTemplates.listSystem();
                    }],
                    'userId': function () {
                        return '1'; //  KMC would need to give us the userID ?
                    }

                }
            }
        );
        $routeProvider.when('/new',
            {templateUrl: 'view/edit.html',
                controller: 'PlayerEditCtrl',
                resolve: {
                    'api': ['api', 'apiService', 'localStorageService', '$location', function (api, apiService, localStorageService, $location) {
                        return ksCheck(api, apiService, localStorageService, $location);
                    }],
                    'PlayerData': function (api, PlayerService) {
                        return api.then(function () {
                            return PlayerService.newPlayer();
                        });
                    },
                    'menuSvc': 'menuSvc'
                }
            }
        );
        $routeProvider.when('/logout', {
            resolve: {'logout': ['localStorageService', 'apiService', '$location', function (localStorageService, apiService, $location) {
                if (localStorageService.isSupported()) {
                    localStorageService.clearAll();
                }
                apiService.unSetks();
                $location.path('/login');
            }]}

        });
        $routeProvider.otherwise({
            resolve: {'res': ['api', 'apiService', 'localStorageService', '$location', function (api, apiService, localStorageService, $location) {
                if (ksCheck(api, apiService, localStorageService, $location)) {
                    return $location.path('/list');
                }
            }]}
        });
    }]).run(function ($rootScope, $rootElement, $location, menuSvc) {
    var appLoad = new Date();
    var debug = false;

    // set the logTime function used in debug mode
    var logTime = function (eventName) {
        if ($location.search()['debug']) {
            var now = new Date();
            var diff = Math.abs(appLoad.getTime() - now.getTime());
            cl(eventName + ' ' + Math.ceil(diff / 1000) + 'sec ' + diff % 1000 + 'ms');
        }
    };
    window.logTime = logTime;
    logTime('AppJsLoad');

    // add functions to $rootScope constructor. $safeApply to prevent apply when digest is already in progress
    $rootScope.constructor.prototype.$safeApply = function (fn) {
        var phase = this.$root.$$phase;
        if (phase == '$apply' || phase == '$digest')
            this.$eval(fn);
        else
            this.$apply(fn);
    };

    // custom show for tooltips
    $rootScope.constructor.prototype.openTooltip = function ($event) {
        if (menuSvc.currentTooltip == $event.target) return;
        menuSvc.currentTooltip = $event.target;
        $($event.target).trigger('customShow');
        $event.preventDefault();
        $event.stopPropagation();
        return false;
    };

    // for css - define the body ID according to route
    $rootScope.routeName = '';
    $rootScope.$on('$routeChangeSuccess', function () {
        appLoad = new Date();
        var url = $location.url().split('/');
        if (debug) {
            $location.search({debug: true});
        }
        if (url[1].indexOf('?') != -1) {
            url[1] = url[1].substr(0, url[1].indexOf('?'));
        }
        $rootScope.routeName = url[1];
    });

    // set debug flag across route changes
    $rootScope.$on('$routeChangeStart', function () {
        if ($location.search()['debug']) {
            debug = true;
        }
        else {
            debug = false;
        }
    });
});
<|endoftext|>


<commit_before>import 'babel-polyfill';	// for those delicious Promises and the like

import assert from 'assert';
import simple from 'simple-mock';
import Requester from '../../src/lib/requester';

/**
 * Unit tests for lib/requester.js. NO arrow functions in test setup description
 * due to `this` binding issues.
**/

describe('Requester', function() {
	describe('Requester.get', function() {
		it('should return a JSON object when requesting from a JSON endpoint', function() {
			let expectedResponse = { json: true };

			simple.mock(Requester, '_fetch').resolveWith(expectedResponse);

			Requester.get('/valid-json-endpoint.json').then((response) => {
				assert.deepEqual(expectedResponse, response);
			});
		});

		it('should return a Blob object when requesting from a non JSON endpoint', function() {
			// Blobs don't exist natively in node
			function Blob() {};
			let expectedResponse = new Blob();

			simple.mock(Requester, '_fetch').resolveWith(expectedResponse);

			Requester.get('/non-json-endpoint.fzxk').then((response) => {
				assert(response instanceof Blob);
			});
		});
	});

	describe('Requester.post', function() {
		it('should return a JSON object when POSTing to a JSON endpoint', function() {
			let expectedResponse = { json: true };

			simple.mock(Requester, '_fetch').resolveWith(expectedResponse);

			Requester.post('/valid-json-endpoint.json', { data: { 'hello': 'world' } }).then((response) => {
				assert.deepEqual(expectedResponse, response);
			});
		});

		it('should return a Blob object when POSTing to a non JSON endpoint', function() {
				function Blob() {};
				let expectedResponse = new Blob();

				simple.mock(Requester, '_fetch').resolveWith(expectedResponse);

				Requester.post('/non-json-endpoint.fzxk', { data: { 'hello': 'world' } }).then((response) => {
					assert.deepEqual(expectedResponse, response);
				});
			});
	});
});<commit_msg>Add an afterEach hook to reset object state after requester unit tests<commit_after>import assert from 'assert';
import simple from 'simple-mock';
import Requester from '../../src/lib/requester';

/**
 * Unit tests for lib/requester.js. NO arrow functions in test setup description
 * due to `this` binding issues.
**/

describe('Requester', function() {
	afterEach(function() {
    simple.restore();	// restores state of objects
  });

	describe('Requester.get', function() {
		it('should return a JSON object when requesting from a JSON endpoint', function() {
			let expectedResponse = { json: true };

			simple.mock(Requester, '_fetch').resolveWith(expectedResponse);

			Requester.get('/valid-json-endpoint.json').then((response) => {
				assert.deepEqual(expectedResponse, response);
			});
		});

		it('should return a Blob object when requesting from a non JSON endpoint', function() {
			// Blobs don't exist natively in node
			function Blob() {};
			let expectedResponse = new Blob();

			simple.mock(Requester, '_fetch').resolveWith(expectedResponse);

			Requester.get('/non-json-endpoint.fzxk').then((response) => {
				assert(response instanceof Blob);
			});
		});
	});

	describe('Requester.post', function() {
		it('should return a JSON object when POSTing to a JSON endpoint', function() {
			let expectedResponse = { json: true };

			simple.mock(Requester, '_fetch').resolveWith(expectedResponse);

			Requester.post('/valid-json-endpoint.json', { data: { 'hello': 'world' } }).then((response) => {
				assert.deepEqual(expectedResponse, response);
			});
		});

		it('should return a Blob object when POSTing to a non JSON endpoint', function() {
				function Blob() {};
				let expectedResponse = new Blob();

				simple.mock(Requester, '_fetch').resolveWith(expectedResponse);

				Requester.post('/non-json-endpoint.fzxk', { data: { 'hello': 'world' } }).then((response) => {
					assert.deepEqual(expectedResponse, response);
				});
			});
	});
});<|endoftext|>


<commit_before>// Copyright 2018, University of Colorado Boulder

/**
 * Tool that displays the (x,y) coordinates of a grid-point on the graph.
 * Origin is at the tip of the tool (bottom center.)
 * Copied from GRAPHING_LINES/common/view/PointToolNode
 *
 * @author Chris Malley (PixelZoom, Inc.)
 * @author Andrea Lin
 */
define( function( require ) {
  'use strict';

  // modules
  const Circle = require( 'SCENERY/nodes/Circle' );
  const GLFont = require( 'GRAPHING_LINES/common/GLFont' );
  const graphingQuadratics = require( 'GRAPHING_QUADRATICS/graphingQuadratics' );
  const Image = require( 'SCENERY/nodes/Image' );
  const Node = require( 'SCENERY/nodes/Node' );
  const Path = require( 'SCENERY/nodes/Path' );
  const Property = require( 'AXON/Property' );
  const Rectangle = require( 'SCENERY/nodes/Rectangle' );
  const Shape = require( 'KITE/Shape' );
  const SimpleDragHandler = require( 'SCENERY/input/SimpleDragHandler' );
  const StringUtils = require( 'PHETCOMMON/util/StringUtils' );
  const Text = require( 'SCENERY/nodes/Text' );
  const Util = require( 'DOT/Util' );
  const Vector2 = require( 'DOT/Vector2' );

  // strings
  const pointUnknownString = require( 'string!GRAPHING_LINES/point.unknown' );
  const pointXYString = require( 'string!GRAPHING_LINES/point.XY' );

  // images
  const bodyImage = require( 'image!GRAPHING_LINES/point_tool_body.png' );

  // constants
  const NUMBER_OF_DECIMAL_PLACES = 1;
  const VALUE_WINDOW_CENTER_X = 44; // center of the value window relative to the left edge of point_tool_body.png
  const CIRCLE_AROUND_CROSSHAIR_RADIUS = 15; // view units, will not be transformed
  const TRANSPARENT_WHITE = 'rgba( 255, 255, 255, 0.2 )';

  class PointToolNode extends Node {

    /**
     * @param {PointTool} pointTool
     * @param {ModelViewTransform2} modelViewTransform
     * @param {Graph} graph
     * @param {Property.<Boolean>} linesVisibleProperty
     * @param {ObservableArray.<Line>} lines Lines that the tool might intersect, provided in the order that they would
     * be rendered
     * @param {Object} [options]
     * @constructor
     */
    constructor( pointTool, modelViewTransform, graph, linesVisibleProperty, lines, options ) {

      options = _.extend( {
        cursor: 'pointer',
        backgroundNormalColor: 'white',
        foregroundNormalColor: 'black',
        foregroundHighlightColor: 'white'
      }, options );

      const bodyNode = new Image( bodyImage, { centerY: 0 } ); // body of the tool

      // crosshair view
      const crosshairShape = new Shape()
        .moveTo( -CIRCLE_AROUND_CROSSHAIR_RADIUS, 0 )
        .lineTo( CIRCLE_AROUND_CROSSHAIR_RADIUS * 1.5, 0 )
        .moveTo( 0, -CIRCLE_AROUND_CROSSHAIR_RADIUS )
        .lineTo( 0, CIRCLE_AROUND_CROSSHAIR_RADIUS );

      const crosshair = new Path( crosshairShape, { stroke: 'black' } );
      const circle = new Circle( CIRCLE_AROUND_CROSSHAIR_RADIUS, {
        lineWidth: 2,
        stroke: 'black',
        fill: TRANSPARENT_WHITE,
        centerX: 0,
        centerY: 0
      } );

      /*
       * Tip, separate from the body and not pickable.
       * Because picking bounds are rectangular, making the tip pickable made it difficult
       * to pick a line manipulator when the tip and manipulator were on the same grid point.
       * Making the tip non-pickable was determined to be an acceptable and 'natural feeling' solution.
       */
      const tipNode = new Node( {
        children: [
          crosshair,
          circle
        ],
        pickable: false,
        x: 0,
        y: 0
      } );

      // background behind the displayed value, shows through a transparent hole in the display area portion of the body
      // image
      const BACKGROUND_MARGIN = 5;
      const backgroundNode = new Rectangle( 0, 0,
        bodyNode.width - ( 2 * BACKGROUND_MARGIN ), bodyNode.height - ( 2 * BACKGROUND_MARGIN ),
        { pickable: false } );

      // displayed value
      const valueNode = new Text( '?', {
        font: new GLFont( { size: 15, weight: 'bold' } ),
        pickable: false,
        maxWidth: 60 // constrain width, determined empirically, dependent on bodyImage
      } );

      // orientation
      if ( pointTool.orientation === 'left' ) {
        bodyNode.left = tipNode.right;
        backgroundNode.centerX = bodyNode.centerX;
        backgroundNode.top = bodyNode.top + BACKGROUND_MARGIN;
        valueNode.centerY = backgroundNode.centerY;
      }
      else if ( pointTool.orientation === 'right' ) {
        tipNode.setScaleMagnitude( -1, 1 ); // reflect around y-axis
        bodyNode.right = tipNode.left;
        backgroundNode.centerX = bodyNode.centerX;
        backgroundNode.top = bodyNode.top + BACKGROUND_MARGIN;
        valueNode.centerY = backgroundNode.centerY;
      }
      else {
        throw new Error( 'unsupported point tool orientation: ' + pointTool.orientation );
      }

      options.children = [
        backgroundNode,
        bodyNode,
        tipNode,
        valueNode
      ];
      super( options );

      // @private
      this.backgroundNode = backgroundNode;
      this.bodyNode = bodyNode;
      this.tipNode = tipNode;
      this.valueNode = valueNode;

      // initial state
      this.setCoordinatesVector2( pointTool.locationProperty.get() );
      this.setBackground( options.backgroundNormalColor );

      // location and display, unmultilink in dispose
      const updateMultilink = Property.multilink( [
          pointTool.locationProperty,
          pointTool.onLineProperty,
          linesVisibleProperty
        ],
        () => {

          // move to location
          let location = pointTool.locationProperty.get();
          this.translation = modelViewTransform.modelToViewPosition( location );

          // display value and highlighting
          if ( graph.contains( location ) ) {
            this.setCoordinatesVector2( location );
            if ( linesVisibleProperty.get() ) {
              // use the line's color to highlight
              this.setForeground( !pointTool.onLineProperty.get() ?
                                  options.foregroundNormalColor : options.foregroundHighlightColor );
              this.setBackground( !pointTool.onLineProperty.get() ?
                                  options.backgroundNormalColor : pointTool.onLineProperty.get().color );
            }
            else {
              this.setForeground( options.foregroundNormalColor );
              this.setBackground( options.backgroundNormalColor );
            }
          }
          else {
            this.setCoordinatesString( pointUnknownString );
            this.setForeground( options.foregroundNormalColor );
            this.setBackground( options.backgroundNormalColor );
          }
        } );

      // interactivity
      this.addInputListener( new PointToolDragHandler( pointTool, modelViewTransform, graph, lines ) );

      // @private called by dispose
      this.disposePointToolNode = function() {
        Property.unmultilink( updateMultilink );
      };
    }

    /**
     * @public
     * @override
     */
    dispose() {
      this.disposePointToolNode();
      Node.prototype.dispose.call( this );
    }

    // @private Sets the displayed value to a point
    setCoordinatesVector2( p ) {
      this.setCoordinatesString( StringUtils.format(
        pointXYString,
        Util.toFixed( p.x, NUMBER_OF_DECIMAL_PLACES ),
        Util.toFixed( p.y, NUMBER_OF_DECIMAL_PLACES )
      ) );
    }

    // @private Sets the displayed value to an arbitrary string
    setCoordinatesString( s ) {
      this.valueNode.text = s;
      this.valueNode.centerX = this.bodyNode.left + VALUE_WINDOW_CENTER_X;  // centered
    }

    // @private Sets the foreground, the color of the displayed value
    setForeground( color ) {
      this.valueNode.fill = color;
    }

    // @private Sets the background, the color of the display area behind the value
    setBackground( color ) {
      this.backgroundNode.fill = color;
    }
  }

  graphingQuadratics.register( 'PointToolNode', PointToolNode );

  class PointToolDragHandler extends SimpleDragHandler {

    /**
     * Drag handler for the pointer tool.
     * @param {PointTool} pointTool
     * @param {ModelViewTransform2} modelViewTransform
     * @param {ObservableArray.<Line>} lines Lines that the tool might intersect, provided in the order that they would
     * be rendered
     * @param {Graph} graph
     */
    constructor( pointTool, modelViewTransform, graph, lines ) {

      let startOffset; // where the drag started, relative to the tool's origin, in parent view coordinates

      const constrainBounds = function( point, bounds ) {
        if ( !bounds || bounds.containsPoint( point ) ) {
          return point;
        }
        else {
          return new Vector2(
            Util.clamp( point.x, bounds.minX, bounds.maxX ),
            Util.clamp( point.y, bounds.minY, bounds.maxY )
          );
        }
      };

      super( {

        allowTouchSnag: true,

        // note where the drag started
        start: function( event ) {
          // Note the mouse-click offset when dragging starts.
          let location = modelViewTransform.modelToViewPosition( pointTool.locationProperty.get() );
          startOffset = event.currentTarget.globalToParentPoint( event.pointer.point ).minus( location );
          // Move the tool that we're dragging to the foreground.
          event.currentTarget.moveToFront();
        },

        drag: function( event ) {
          let parentPoint = event.currentTarget.globalToParentPoint( event.pointer.point ).minus( startOffset );
          let location = modelViewTransform.viewToModelPosition( parentPoint );
          location = constrainBounds( location, pointTool.dragBounds );
          if ( graph.contains( location ) ) {
            let line;
            let nearestPoint;
            let distance = 1; // empirically chosen
            for ( let i = 0; i < lines.length; i ++ ) {
              // snap to line if near
              line = lines.get( i );
              nearestPoint = line.nearestPointOnLineToPoint( location );
              if ( nearestPoint.distance( location ) < distance ) {
                distance = nearestPoint.distance( location );
                location = nearestPoint;
              }
            }
            if ( distance === 1 ) { // didn't find a quadratic nearby
              // snap to the graph's grid
              location = new Vector2( Util.toFixedNumber( location.x, 0 ), Util.toFixedNumber( location.y, 0 ) );
            }
          }

          pointTool.locationProperty.set( location );
        }
      } );
    }
  }

  graphingQuadratics.register( 'PointToolNode.PointToolDragHandler', PointToolDragHandler );

  return PointToolNode;
} );<commit_msg>empty line before comment<commit_after>// Copyright 2018, University of Colorado Boulder

/**
 * Tool that displays the (x,y) coordinates of a grid-point on the graph.
 * Origin is at the tip of the tool (bottom center.)
 * Copied from GRAPHING_LINES/common/view/PointToolNode
 *
 * @author Chris Malley (PixelZoom, Inc.)
 * @author Andrea Lin
 */
define( function( require ) {
  'use strict';

  // modules
  const Circle = require( 'SCENERY/nodes/Circle' );
  const GLFont = require( 'GRAPHING_LINES/common/GLFont' );
  const graphingQuadratics = require( 'GRAPHING_QUADRATICS/graphingQuadratics' );
  const Image = require( 'SCENERY/nodes/Image' );
  const Node = require( 'SCENERY/nodes/Node' );
  const Path = require( 'SCENERY/nodes/Path' );
  const Property = require( 'AXON/Property' );
  const Rectangle = require( 'SCENERY/nodes/Rectangle' );
  const Shape = require( 'KITE/Shape' );
  const SimpleDragHandler = require( 'SCENERY/input/SimpleDragHandler' );
  const StringUtils = require( 'PHETCOMMON/util/StringUtils' );
  const Text = require( 'SCENERY/nodes/Text' );
  const Util = require( 'DOT/Util' );
  const Vector2 = require( 'DOT/Vector2' );

  // strings
  const pointUnknownString = require( 'string!GRAPHING_LINES/point.unknown' );
  const pointXYString = require( 'string!GRAPHING_LINES/point.XY' );

  // images
  const bodyImage = require( 'image!GRAPHING_LINES/point_tool_body.png' );

  // constants
  const NUMBER_OF_DECIMAL_PLACES = 1;
  const VALUE_WINDOW_CENTER_X = 44; // center of the value window relative to the left edge of point_tool_body.png
  const CIRCLE_AROUND_CROSSHAIR_RADIUS = 15; // view units, will not be transformed
  const TRANSPARENT_WHITE = 'rgba( 255, 255, 255, 0.2 )';

  class PointToolNode extends Node {

    /**
     * @param {PointTool} pointTool
     * @param {ModelViewTransform2} modelViewTransform
     * @param {Graph} graph
     * @param {Property.<Boolean>} linesVisibleProperty
     * @param {ObservableArray.<Line>} lines Lines that the tool might intersect, provided in the order that they would
     * be rendered
     * @param {Object} [options]
     * @constructor
     */
    constructor( pointTool, modelViewTransform, graph, linesVisibleProperty, lines, options ) {

      options = _.extend( {
        cursor: 'pointer',
        backgroundNormalColor: 'white',
        foregroundNormalColor: 'black',
        foregroundHighlightColor: 'white'
      }, options );

      const bodyNode = new Image( bodyImage, { centerY: 0 } ); // body of the tool

      // crosshair view
      const crosshairShape = new Shape()
        .moveTo( -CIRCLE_AROUND_CROSSHAIR_RADIUS, 0 )
        .lineTo( CIRCLE_AROUND_CROSSHAIR_RADIUS * 1.5, 0 )
        .moveTo( 0, -CIRCLE_AROUND_CROSSHAIR_RADIUS )
        .lineTo( 0, CIRCLE_AROUND_CROSSHAIR_RADIUS );

      const crosshair = new Path( crosshairShape, { stroke: 'black' } );
      const circle = new Circle( CIRCLE_AROUND_CROSSHAIR_RADIUS, {
        lineWidth: 2,
        stroke: 'black',
        fill: TRANSPARENT_WHITE,
        centerX: 0,
        centerY: 0
      } );

      /*
       * Tip, separate from the body and not pickable.
       * Because picking bounds are rectangular, making the tip pickable made it difficult
       * to pick a line manipulator when the tip and manipulator were on the same grid point.
       * Making the tip non-pickable was determined to be an acceptable and 'natural feeling' solution.
       */
      const tipNode = new Node( {
        children: [
          crosshair,
          circle
        ],
        pickable: false,
        x: 0,
        y: 0
      } );

      // background behind the displayed value, shows through a transparent hole in the display area portion of the body
      // image
      const BACKGROUND_MARGIN = 5;
      const backgroundNode = new Rectangle( 0, 0,
        bodyNode.width - ( 2 * BACKGROUND_MARGIN ), bodyNode.height - ( 2 * BACKGROUND_MARGIN ),
        { pickable: false } );

      // displayed value
      const valueNode = new Text( '?', {
        font: new GLFont( { size: 15, weight: 'bold' } ),
        pickable: false,
        maxWidth: 60 // constrain width, determined empirically, dependent on bodyImage
      } );

      // orientation
      if ( pointTool.orientation === 'left' ) {
        bodyNode.left = tipNode.right;
        backgroundNode.centerX = bodyNode.centerX;
        backgroundNode.top = bodyNode.top + BACKGROUND_MARGIN;
        valueNode.centerY = backgroundNode.centerY;
      }
      else if ( pointTool.orientation === 'right' ) {
        tipNode.setScaleMagnitude( -1, 1 ); // reflect around y-axis
        bodyNode.right = tipNode.left;
        backgroundNode.centerX = bodyNode.centerX;
        backgroundNode.top = bodyNode.top + BACKGROUND_MARGIN;
        valueNode.centerY = backgroundNode.centerY;
      }
      else {
        throw new Error( 'unsupported point tool orientation: ' + pointTool.orientation );
      }

      options.children = [
        backgroundNode,
        bodyNode,
        tipNode,
        valueNode
      ];
      super( options );

      // @private
      this.backgroundNode = backgroundNode;
      this.bodyNode = bodyNode;
      this.tipNode = tipNode;
      this.valueNode = valueNode;

      // initial state
      this.setCoordinatesVector2( pointTool.locationProperty.get() );
      this.setBackground( options.backgroundNormalColor );

      // location and display, unmultilink in dispose
      const updateMultilink = Property.multilink( [
          pointTool.locationProperty,
          pointTool.onLineProperty,
          linesVisibleProperty
        ],
        () => {

          // move to location
          let location = pointTool.locationProperty.get();
          this.translation = modelViewTransform.modelToViewPosition( location );

          // display value and highlighting
          if ( graph.contains( location ) ) {
            this.setCoordinatesVector2( location );
            if ( linesVisibleProperty.get() ) {
              // use the line's color to highlight
              this.setForeground( !pointTool.onLineProperty.get() ?
                                  options.foregroundNormalColor : options.foregroundHighlightColor );
              this.setBackground( !pointTool.onLineProperty.get() ?
                                  options.backgroundNormalColor : pointTool.onLineProperty.get().color );
            }
            else {
              this.setForeground( options.foregroundNormalColor );
              this.setBackground( options.backgroundNormalColor );
            }
          }
          else {
            this.setCoordinatesString( pointUnknownString );
            this.setForeground( options.foregroundNormalColor );
            this.setBackground( options.backgroundNormalColor );
          }
        } );

      // interactivity
      this.addInputListener( new PointToolDragHandler( pointTool, modelViewTransform, graph, lines ) );

      // @private called by dispose
      this.disposePointToolNode = function() {
        Property.unmultilink( updateMultilink );
      };
    }

    /**
     * @public
     * @override
     */
    dispose() {
      this.disposePointToolNode();
      Node.prototype.dispose.call( this );
    }

    // @private Sets the displayed value to a point
    setCoordinatesVector2( p ) {
      this.setCoordinatesString( StringUtils.format(
        pointXYString,
        Util.toFixed( p.x, NUMBER_OF_DECIMAL_PLACES ),
        Util.toFixed( p.y, NUMBER_OF_DECIMAL_PLACES )
      ) );
    }

    // @private Sets the displayed value to an arbitrary string
    setCoordinatesString( s ) {
      this.valueNode.text = s;
      this.valueNode.centerX = this.bodyNode.left + VALUE_WINDOW_CENTER_X;  // centered
    }

    // @private Sets the foreground, the color of the displayed value
    setForeground( color ) {
      this.valueNode.fill = color;
    }

    // @private Sets the background, the color of the display area behind the value
    setBackground( color ) {
      this.backgroundNode.fill = color;
    }
  }

  graphingQuadratics.register( 'PointToolNode', PointToolNode );

  class PointToolDragHandler extends SimpleDragHandler {

    /**
     * Drag handler for the pointer tool.
     * @param {PointTool} pointTool
     * @param {ModelViewTransform2} modelViewTransform
     * @param {ObservableArray.<Line>} lines Lines that the tool might intersect, provided in the order that they would
     * be rendered
     * @param {Graph} graph
     */
    constructor( pointTool, modelViewTransform, graph, lines ) {

      let startOffset; // where the drag started, relative to the tool's origin, in parent view coordinates

      const constrainBounds = function( point, bounds ) {
        if ( !bounds || bounds.containsPoint( point ) ) {
          return point;
        }
        else {
          return new Vector2(
            Util.clamp( point.x, bounds.minX, bounds.maxX ),
            Util.clamp( point.y, bounds.minY, bounds.maxY )
          );
        }
      };

      super( {

        allowTouchSnag: true,

        // note where the drag started
        start: function( event ) {
          // Note the mouse-click offset when dragging starts.
          let location = modelViewTransform.modelToViewPosition( pointTool.locationProperty.get() );
          startOffset = event.currentTarget.globalToParentPoint( event.pointer.point ).minus( location );
          // Move the tool that we're dragging to the foreground.
          event.currentTarget.moveToFront();
        },

        drag: function( event ) {
          let parentPoint = event.currentTarget.globalToParentPoint( event.pointer.point ).minus( startOffset );
          let location = modelViewTransform.viewToModelPosition( parentPoint );
          location = constrainBounds( location, pointTool.dragBounds );
          if ( graph.contains( location ) ) {
            let line;
            let nearestPoint;
            let distance = 1; // empirically chosen
            for ( let i = 0; i < lines.length; i ++ ) {

              // snap to line if near
              line = lines.get( i );
              nearestPoint = line.nearestPointOnLineToPoint( location );
              if ( nearestPoint.distance( location ) < distance ) {
                distance = nearestPoint.distance( location );
                location = nearestPoint;
              }
            }
            if ( distance === 1 ) { // didn't find a quadratic nearby
              // snap to the graph's grid
              location = new Vector2( Util.toFixedNumber( location.x, 0 ), Util.toFixedNumber( location.y, 0 ) );
            }
          }

          pointTool.locationProperty.set( location );
        }
      } );
    }
  }

  graphingQuadratics.register( 'PointToolNode.PointToolDragHandler', PointToolDragHandler );

  return PointToolNode;
} );<|endoftext|>